@fluidframework/tree 2.82.0 → 2.83.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.
- package/CHANGELOG.md +43 -0
- package/README.md +33 -5
- package/api-report/tree.alpha.api.md +25 -21
- package/api-report/tree.beta.api.md +14 -2
- package/api-report/tree.legacy.beta.api.md +14 -2
- package/api-report/tree.legacy.public.api.md +1 -1
- package/api-report/tree.public.api.md +1 -1
- package/dist/alpha.d.ts +3 -3
- package/dist/beta.d.ts +1 -0
- package/dist/codec/codec.d.ts +3 -39
- package/dist/codec/codec.d.ts.map +1 -1
- package/dist/codec/codec.js +5 -50
- package/dist/codec/codec.js.map +1 -1
- package/dist/codec/index.d.ts +1 -1
- package/dist/codec/index.d.ts.map +1 -1
- package/dist/codec/index.js +1 -2
- package/dist/codec/index.js.map +1 -1
- package/dist/codec/versioned/codec.d.ts +20 -7
- package/dist/codec/versioned/codec.d.ts.map +1 -1
- package/dist/codec/versioned/codec.js +56 -30
- package/dist/codec/versioned/codec.js.map +1 -1
- package/dist/core/tree/detachedFieldIndexCodecs.d.ts.map +1 -1
- package/dist/core/tree/detachedFieldIndexCodecs.js +6 -4
- package/dist/core/tree/detachedFieldIndexCodecs.js.map +1 -1
- package/dist/extensibleUnionNode.d.ts +97 -0
- package/dist/extensibleUnionNode.d.ts.map +1 -0
- package/dist/{extensibleSchemaUnion.js → extensibleUnionNode.js} +28 -18
- package/dist/extensibleUnionNode.js.map +1 -0
- package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts +1 -1
- package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/codecs.js +4 -4
- package/dist/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
- package/dist/feature-libraries/forest-summary/codec.d.ts.map +1 -1
- package/dist/feature-libraries/forest-summary/codec.js +7 -1
- package/dist/feature-libraries/forest-summary/codec.js.map +1 -1
- package/dist/feature-libraries/forest-summary/formatCommon.d.ts +3 -3
- package/dist/feature-libraries/forest-summary/formatCommon.d.ts.map +1 -1
- package/dist/feature-libraries/forest-summary/formatCommon.js.map +1 -1
- package/dist/feature-libraries/forest-summary/formatV1.d.ts +2 -3
- package/dist/feature-libraries/forest-summary/formatV1.d.ts.map +1 -1
- package/dist/feature-libraries/forest-summary/formatV1.js +1 -2
- package/dist/feature-libraries/forest-summary/formatV1.js.map +1 -1
- package/dist/feature-libraries/forest-summary/formatV2.d.ts +2 -3
- package/dist/feature-libraries/forest-summary/formatV2.d.ts.map +1 -1
- package/dist/feature-libraries/forest-summary/formatV2.js +1 -2
- package/dist/feature-libraries/forest-summary/formatV2.js.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeCodecV1.d.ts +2 -2
- package/dist/feature-libraries/modular-schema/modularChangeCodecV1.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeCodecV1.js +4 -4
- package/dist/feature-libraries/modular-schema/modularChangeCodecV1.js.map +1 -1
- package/dist/feature-libraries/schema-index/codec.d.ts.map +1 -1
- package/dist/feature-libraries/schema-index/codec.js +6 -4
- package/dist/feature-libraries/schema-index/codec.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/shared-tree/sharedTreeChangeCodecs.js +1 -1
- package/dist/shared-tree/sharedTreeChangeCodecs.js.map +1 -1
- package/dist/shared-tree/tree.d.ts +1 -1
- package/dist/shared-tree/tree.js.map +1 -1
- package/dist/shared-tree/treeAlpha.d.ts +1 -1
- package/dist/shared-tree/treeAlpha.js.map +1 -1
- package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
- package/dist/shared-tree/treeCheckout.js +2 -4
- package/dist/shared-tree/treeCheckout.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsCommons.d.ts +3 -3
- package/dist/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsCommons.js +2 -2
- package/dist/shared-tree-core/editManagerCodecsCommons.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts +2 -2
- package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsV1toV4.js +1 -1
- package/dist/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsVSharedBranches.d.ts +2 -2
- package/dist/shared-tree-core/editManagerCodecsVSharedBranches.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsVSharedBranches.js +1 -1
- package/dist/shared-tree-core/editManagerCodecsVSharedBranches.js.map +1 -1
- package/dist/shared-tree-core/messageCodecs.d.ts.map +1 -1
- package/dist/shared-tree-core/messageCodecs.js +2 -2
- package/dist/shared-tree-core/messageCodecs.js.map +1 -1
- package/dist/simple-tree/api/index.d.ts +1 -1
- package/dist/simple-tree/api/index.d.ts.map +1 -1
- package/dist/simple-tree/api/index.js +2 -2
- package/dist/simple-tree/api/index.js.map +1 -1
- package/dist/simple-tree/api/snapshotCompatibilityChecker.d.ts +148 -29
- package/dist/simple-tree/api/snapshotCompatibilityChecker.d.ts.map +1 -1
- package/dist/simple-tree/api/snapshotCompatibilityChecker.js +180 -99
- package/dist/simple-tree/api/snapshotCompatibilityChecker.js.map +1 -1
- package/dist/simple-tree/api/tree.d.ts +1 -1
- package/dist/simple-tree/api/tree.js.map +1 -1
- package/dist/simple-tree/api/treeBeta.d.ts +1 -1
- package/dist/simple-tree/api/treeBeta.js.map +1 -1
- package/dist/simple-tree/core/allowedTypes.d.ts +1 -1
- package/dist/simple-tree/core/allowedTypes.js.map +1 -1
- package/dist/simple-tree/core/unhydratedFlexTree.d.ts +1 -0
- package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
- package/dist/simple-tree/core/unhydratedFlexTree.js +29 -0
- package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
- package/dist/simple-tree/index.d.ts +1 -1
- package/dist/simple-tree/index.d.ts.map +1 -1
- package/dist/simple-tree/index.js +2 -2
- package/dist/simple-tree/index.js.map +1 -1
- package/dist/simple-tree/node-kinds/array/arrayNode.d.ts.map +1 -1
- package/dist/simple-tree/node-kinds/array/arrayNode.js +4 -13
- package/dist/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
- package/dist/simple-tree/unhydratedFlexTreeFromInsertable.d.ts.map +1 -1
- package/dist/simple-tree/unhydratedFlexTreeFromInsertable.js +33 -7
- package/dist/simple-tree/unhydratedFlexTreeFromInsertable.js.map +1 -1
- package/dist/text/textDomainFormatted.d.ts +3 -3
- package/dist/text/textDomainFormatted.d.ts.map +1 -1
- package/dist/text/textDomainFormatted.js +48 -32
- package/dist/text/textDomainFormatted.js.map +1 -1
- package/dist/util/bTreeUtils.d.ts.map +1 -1
- package/dist/util/bTreeUtils.js +6 -6
- package/dist/util/bTreeUtils.js.map +1 -1
- package/dist/util/rangeMap.d.ts.map +1 -1
- package/dist/util/rangeMap.js +5 -6
- package/dist/util/rangeMap.js.map +1 -1
- package/lib/alpha.d.ts +3 -3
- package/lib/beta.d.ts +1 -0
- package/lib/codec/codec.d.ts +3 -39
- package/lib/codec/codec.d.ts.map +1 -1
- package/lib/codec/codec.js +4 -47
- package/lib/codec/codec.js.map +1 -1
- package/lib/codec/index.d.ts +1 -1
- package/lib/codec/index.d.ts.map +1 -1
- package/lib/codec/index.js +1 -1
- package/lib/codec/index.js.map +1 -1
- package/lib/codec/versioned/codec.d.ts +20 -7
- package/lib/codec/versioned/codec.d.ts.map +1 -1
- package/lib/codec/versioned/codec.js +59 -33
- package/lib/codec/versioned/codec.js.map +1 -1
- package/lib/core/tree/detachedFieldIndexCodecs.d.ts.map +1 -1
- package/lib/core/tree/detachedFieldIndexCodecs.js +6 -4
- package/lib/core/tree/detachedFieldIndexCodecs.js.map +1 -1
- package/lib/extensibleUnionNode.d.ts +97 -0
- package/lib/extensibleUnionNode.d.ts.map +1 -0
- package/lib/{extensibleSchemaUnion.js → extensibleUnionNode.js} +28 -18
- package/lib/extensibleUnionNode.js.map +1 -0
- package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts +1 -1
- package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/codecs.js +5 -5
- package/lib/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
- package/lib/feature-libraries/forest-summary/codec.d.ts.map +1 -1
- package/lib/feature-libraries/forest-summary/codec.js +8 -2
- package/lib/feature-libraries/forest-summary/codec.js.map +1 -1
- package/lib/feature-libraries/forest-summary/formatCommon.d.ts +3 -3
- package/lib/feature-libraries/forest-summary/formatCommon.d.ts.map +1 -1
- package/lib/feature-libraries/forest-summary/formatCommon.js.map +1 -1
- package/lib/feature-libraries/forest-summary/formatV1.d.ts +2 -3
- package/lib/feature-libraries/forest-summary/formatV1.d.ts.map +1 -1
- package/lib/feature-libraries/forest-summary/formatV1.js +1 -2
- package/lib/feature-libraries/forest-summary/formatV1.js.map +1 -1
- package/lib/feature-libraries/forest-summary/formatV2.d.ts +2 -3
- package/lib/feature-libraries/forest-summary/formatV2.d.ts.map +1 -1
- package/lib/feature-libraries/forest-summary/formatV2.js +1 -2
- package/lib/feature-libraries/forest-summary/formatV2.js.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeCodecV1.d.ts +2 -2
- package/lib/feature-libraries/modular-schema/modularChangeCodecV1.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeCodecV1.js +4 -4
- package/lib/feature-libraries/modular-schema/modularChangeCodecV1.js.map +1 -1
- package/lib/feature-libraries/schema-index/codec.d.ts.map +1 -1
- package/lib/feature-libraries/schema-index/codec.js +6 -4
- package/lib/feature-libraries/schema-index/codec.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/shared-tree/sharedTreeChangeCodecs.js +1 -1
- package/lib/shared-tree/sharedTreeChangeCodecs.js.map +1 -1
- package/lib/shared-tree/tree.d.ts +1 -1
- package/lib/shared-tree/tree.js.map +1 -1
- package/lib/shared-tree/treeAlpha.d.ts +1 -1
- package/lib/shared-tree/treeAlpha.js.map +1 -1
- package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
- package/lib/shared-tree/treeCheckout.js +2 -4
- package/lib/shared-tree/treeCheckout.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsCommons.d.ts +3 -3
- package/lib/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsCommons.js +2 -2
- package/lib/shared-tree-core/editManagerCodecsCommons.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts +2 -2
- package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsV1toV4.js +2 -2
- package/lib/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsVSharedBranches.d.ts +2 -2
- package/lib/shared-tree-core/editManagerCodecsVSharedBranches.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsVSharedBranches.js +2 -2
- package/lib/shared-tree-core/editManagerCodecsVSharedBranches.js.map +1 -1
- package/lib/shared-tree-core/messageCodecs.d.ts.map +1 -1
- package/lib/shared-tree-core/messageCodecs.js +2 -2
- package/lib/shared-tree-core/messageCodecs.js.map +1 -1
- package/lib/simple-tree/api/index.d.ts +1 -1
- package/lib/simple-tree/api/index.d.ts.map +1 -1
- package/lib/simple-tree/api/index.js +1 -1
- package/lib/simple-tree/api/index.js.map +1 -1
- package/lib/simple-tree/api/snapshotCompatibilityChecker.d.ts +148 -29
- package/lib/simple-tree/api/snapshotCompatibilityChecker.d.ts.map +1 -1
- package/lib/simple-tree/api/snapshotCompatibilityChecker.js +179 -98
- package/lib/simple-tree/api/snapshotCompatibilityChecker.js.map +1 -1
- package/lib/simple-tree/api/tree.d.ts +1 -1
- package/lib/simple-tree/api/tree.js.map +1 -1
- package/lib/simple-tree/api/treeBeta.d.ts +1 -1
- package/lib/simple-tree/api/treeBeta.js.map +1 -1
- package/lib/simple-tree/core/allowedTypes.d.ts +1 -1
- package/lib/simple-tree/core/allowedTypes.js.map +1 -1
- package/lib/simple-tree/core/unhydratedFlexTree.d.ts +1 -0
- package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
- package/lib/simple-tree/core/unhydratedFlexTree.js +29 -0
- package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
- package/lib/simple-tree/index.d.ts +1 -1
- package/lib/simple-tree/index.d.ts.map +1 -1
- package/lib/simple-tree/index.js +1 -1
- package/lib/simple-tree/index.js.map +1 -1
- package/lib/simple-tree/node-kinds/array/arrayNode.d.ts.map +1 -1
- package/lib/simple-tree/node-kinds/array/arrayNode.js +5 -14
- package/lib/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
- package/lib/simple-tree/unhydratedFlexTreeFromInsertable.d.ts.map +1 -1
- package/lib/simple-tree/unhydratedFlexTreeFromInsertable.js +34 -8
- package/lib/simple-tree/unhydratedFlexTreeFromInsertable.js.map +1 -1
- package/lib/text/textDomainFormatted.d.ts +3 -3
- package/lib/text/textDomainFormatted.d.ts.map +1 -1
- package/lib/text/textDomainFormatted.js +30 -14
- package/lib/text/textDomainFormatted.js.map +1 -1
- package/lib/util/bTreeUtils.d.ts.map +1 -1
- package/lib/util/bTreeUtils.js +6 -6
- package/lib/util/bTreeUtils.js.map +1 -1
- package/lib/util/rangeMap.d.ts.map +1 -1
- package/lib/util/rangeMap.js +5 -6
- package/lib/util/rangeMap.js.map +1 -1
- package/package.json +23 -23
- package/src/codec/codec.ts +10 -112
- package/src/codec/index.ts +0 -3
- package/src/codec/versioned/codec.ts +119 -83
- package/src/core/tree/detachedFieldIndexCodecs.ts +6 -4
- package/src/{extensibleSchemaUnion.ts → extensibleUnionNode.ts} +61 -19
- package/src/feature-libraries/chunked-forest/codec/codecs.ts +5 -11
- package/src/feature-libraries/forest-summary/codec.ts +8 -7
- package/src/feature-libraries/forest-summary/formatCommon.ts +5 -3
- package/src/feature-libraries/forest-summary/formatV1.ts +1 -3
- package/src/feature-libraries/forest-summary/formatV2.ts +1 -3
- package/src/feature-libraries/modular-schema/modularChangeCodecV1.ts +5 -6
- package/src/feature-libraries/schema-index/codec.ts +6 -4
- package/src/index.ts +3 -3
- package/src/packageVersion.ts +1 -1
- package/src/shared-tree/sharedTreeChangeCodecs.ts +2 -2
- package/src/shared-tree/tree.ts +1 -1
- package/src/shared-tree/treeAlpha.ts +1 -1
- package/src/shared-tree/treeCheckout.ts +2 -4
- package/src/shared-tree-core/editManagerCodecsCommons.ts +7 -7
- package/src/shared-tree-core/editManagerCodecsV1toV4.ts +3 -10
- package/src/shared-tree-core/editManagerCodecsVSharedBranches.ts +3 -10
- package/src/shared-tree-core/messageCodecs.ts +2 -6
- package/src/simple-tree/api/index.ts +2 -2
- package/src/simple-tree/api/snapshotCompatibilityChecker.ts +344 -142
- package/src/simple-tree/api/tree.ts +1 -1
- package/src/simple-tree/api/treeBeta.ts +1 -1
- package/src/simple-tree/core/allowedTypes.ts +1 -1
- package/src/simple-tree/core/unhydratedFlexTree.ts +43 -1
- package/src/simple-tree/index.ts +2 -2
- package/src/simple-tree/node-kinds/array/arrayNode.ts +13 -19
- package/src/simple-tree/unhydratedFlexTreeFromInsertable.ts +51 -10
- package/src/text/textDomainFormatted.ts +37 -17
- package/src/util/bTreeUtils.ts +10 -6
- package/src/util/rangeMap.ts +9 -6
- package/api-extractor-lint.json +0 -4
- package/dist/extensibleSchemaUnion.d.ts +0 -72
- package/dist/extensibleSchemaUnion.d.ts.map +0 -1
- package/dist/extensibleSchemaUnion.js.map +0 -1
- package/lib/extensibleSchemaUnion.d.ts +0 -72
- package/lib/extensibleSchemaUnion.d.ts.map +0 -1
- package/lib/extensibleSchemaUnion.js.map +0 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { assert, transformMapValues } from "@fluidframework/core-utils/internal";
|
|
6
|
+
import { assert, fail, transformMapValues } from "@fluidframework/core-utils/internal";
|
|
7
7
|
import { selectVersionRoundedDown } from "@fluidframework/runtime-utils/internal";
|
|
8
8
|
import { UsageError } from "@fluidframework/telemetry-utils/internal";
|
|
9
9
|
import * as semver from "semver-ts";
|
|
@@ -149,7 +149,7 @@ export function importCompatibilitySchemaSnapshot(
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
* The file system methods required by {@link
|
|
152
|
+
* The file system methods required by {@link snapshotSchemaCompatibility}.
|
|
153
153
|
* @remarks
|
|
154
154
|
* Implemented by both Node.js `fs` and `path` modules, but other implementations can be provided as needed.
|
|
155
155
|
*
|
|
@@ -227,36 +227,83 @@ export interface CombinedSchemaCompatibilityStatus {
|
|
|
227
227
|
* How a {@link TreeView} using the snapshotted schema would report its compatibility with a document created with the current schema.
|
|
228
228
|
*/
|
|
229
229
|
readonly snapshotViewOfCurrentDocument: Omit<SchemaCompatibilityStatus, "canInitialize">;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* True if and only if the schema have identical compatibility.
|
|
233
|
+
* @remarks
|
|
234
|
+
* This includes producing the equivalent stored schema (which currentViewOfSnapshotDocument and snapshotViewOfCurrentDocument also measure)
|
|
235
|
+
* as well as equivalent compatibility with potential future schema changes beyond just those in these two schema.
|
|
236
|
+
*
|
|
237
|
+
* This includes compatibility with all potential future schema changes.
|
|
238
|
+
* For example two schema different only in compatibility with future optional fields via allow unknown optional fields or staged schema
|
|
239
|
+
* would be considered non-equivalent, even though they are forwards and backwards compatible with each other, and both status above report them as equivalent
|
|
240
|
+
* since they would produce the same stored schema upon schema upgrade.
|
|
241
|
+
*/
|
|
242
|
+
readonly identicalCompatibility: boolean;
|
|
230
243
|
}
|
|
231
244
|
|
|
232
245
|
/**
|
|
233
|
-
* The options for {@link
|
|
246
|
+
* The options for {@link snapshotSchemaCompatibility}.
|
|
234
247
|
* @input
|
|
235
248
|
* @alpha
|
|
236
249
|
*/
|
|
237
|
-
export interface
|
|
250
|
+
export interface SnapshotSchemaCompatibilityOptions {
|
|
238
251
|
/**
|
|
239
252
|
* Directory where historical schema snapshots are stored.
|
|
253
|
+
* @remarks
|
|
254
|
+
* As the contents of this directory (specifically historical snapshots) cannot be regenerated,
|
|
255
|
+
* a directory appropriate for test data should be used.
|
|
256
|
+
* Generally this means that this directory should be versioned like code,
|
|
257
|
+
* and not erased when regenerating snapshots.
|
|
258
|
+
*
|
|
259
|
+
* This directory will be created if it does not already exist.
|
|
260
|
+
* All ".json" files in this directory will be treated as schema snapshots.
|
|
261
|
+
* It is recommended to use a dedicated directory for each {@link snapshotSchemaCompatibility} powered test.
|
|
262
|
+
*
|
|
263
|
+
* This can use any path syntax supported by the provided {@link SnapshotSchemaCompatibilityOptions.fileSystem}.
|
|
240
264
|
*/
|
|
241
265
|
readonly snapshotDirectory: string;
|
|
266
|
+
|
|
242
267
|
/**
|
|
243
268
|
* How the `snapshotDirectory` is accessed.
|
|
244
269
|
*/
|
|
245
270
|
readonly fileSystem: SnapshotFileSystem;
|
|
246
|
-
|
|
247
|
-
* The current application or library version.
|
|
248
|
-
* @remarks
|
|
249
|
-
* This uses the {@link https://semver.org/#spec-item-11|ordering defined by semver}.
|
|
250
|
-
* It is only compared against the version from previous snapshots (taken from this version when they were created by setting `mode` to "update") and the `minVersionForCollaboration`.
|
|
251
|
-
*/
|
|
252
|
-
readonly version: string;
|
|
271
|
+
|
|
253
272
|
/**
|
|
254
273
|
* The current view schema.
|
|
255
274
|
*/
|
|
256
275
|
readonly schema: TreeViewConfiguration;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The version which will be associated with this version of the schema.
|
|
279
|
+
* @remarks
|
|
280
|
+
* Often the easiest way to ensure this is to simply use the next version which will be released for the package or application itself, and set the `minVersionForCollaboration` based on telemetry about which versions are still in use.
|
|
281
|
+
* To do this, it is recommended that this version be programmatically derived from the application version rather than hard coded inline.
|
|
282
|
+
* For example, reading it from the `package.json` or some other source of truth can be done to ensure it is kept up to date, and thus snapshots always have the correct version.
|
|
283
|
+
* The version used should typically be the _next_ production version (whose formats must be supported long term) that will be released (but is not yet released).
|
|
284
|
+
* This usually means that the correct version to use is the same version that would be used when releasing the application or library, but with any prerelease version tags removed.
|
|
285
|
+
* If an automated way to keep this version up to date is not used, be very careful when reviewing changes to snapshot files to ensure the version is correct.
|
|
286
|
+
* If incorrectly versioned snapshots were committed accidentally, rename the snapshot files to have the correct version, and restore the old files from, version control.
|
|
287
|
+
*
|
|
288
|
+
* It is possible to use a different versioning scheme, for example one specific to the schema in question.
|
|
289
|
+
* This can be done robustly as long as care is taken to ensure the version increases such that every released version has a unique `version` (and therefore unique snapshot),
|
|
290
|
+
* and `minVersionForCollaboration` is set appropriately using the same versioning scheme.
|
|
291
|
+
* {@link SnapshotSchemaCompatibilityOptions.rejectVersionsWithNoSchemaChange} and
|
|
292
|
+
* {@link SnapshotSchemaCompatibilityOptions.rejectSchemaChangesWithNoVersionChange}
|
|
293
|
+
* can be used to help enforce the expected relationship between version changes and schema changes in such cases.
|
|
294
|
+
*
|
|
295
|
+
* Can use any format supported by {@link SnapshotSchemaCompatibilityOptions.versionComparer}.
|
|
296
|
+
* Only compared against the version from previous snapshots (taken from this version when they were created by setting `mode` to "update") and the `minVersionForCollaboration`.
|
|
297
|
+
*
|
|
298
|
+
* Typically `minVersionForCollaboration` should be set to the oldest version currently in use, so it's helpful to use a version which can be easily measured to tell if clients are still using it.
|
|
299
|
+
*/
|
|
300
|
+
readonly version: string;
|
|
301
|
+
|
|
257
302
|
/**
|
|
258
303
|
* The minimum version that the current version is expected to be able to collaborate with.
|
|
259
304
|
* @remarks
|
|
305
|
+
* Can use any format supported by {@link SnapshotSchemaCompatibilityOptions.versionComparer}.
|
|
306
|
+
*
|
|
260
307
|
* This defines a range of versions whose schema must be forwards compatible with trees using the current schema:
|
|
261
308
|
* Any schema from snapshots with a version greater than or equal to this must be able to view documents created with the current schema.
|
|
262
309
|
* This means that if the current `schema` is used to create a {@link TreeView}, then {@link TreeView.upgradeSchema} is used, the older clients,
|
|
@@ -265,13 +312,24 @@ export interface SchemaCompatibilitySnapshotsOptions {
|
|
|
265
312
|
* Typically applications will attempt to manage their deployment/update schedule such that all versions concurrently deployed can
|
|
266
313
|
* collaborate to avoid users losing access to documents when other users upgrade the schema.
|
|
267
314
|
* Such applications can set this to the oldest version currently deployed,
|
|
268
|
-
* then rely on {@link
|
|
315
|
+
* then rely on {@link snapshotSchemaCompatibility} to verify that no schema changes are made which would break collaboration with that (or newer) versions.
|
|
269
316
|
*
|
|
270
317
|
* This is the same approach used by {@link @fluidframework/runtime-definitions#MinimumVersionForCollab}
|
|
271
318
|
* except that type is specifically for use with the version of the Fluid Framework client packages,
|
|
272
|
-
* and this corresponds to
|
|
319
|
+
* and this corresponds to whatever versioning scheme is used with {@link SnapshotSchemaCompatibilityOptions.version}.
|
|
273
320
|
*/
|
|
274
321
|
readonly minVersionForCollaboration: string;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* A comparison function for version strings.
|
|
325
|
+
* @remarks
|
|
326
|
+
* A comparison function like that provided to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#comparefn | Array.sort}.
|
|
327
|
+
* This is used to partition snapshots into those less than `minVersionForCollaboration` and those greater than or equal to it, as well as to sanity check `version` against the versions of the snapshots.
|
|
328
|
+
* If not provided, the ordering is defined by {@link https://semver.org/#spec-item-11|semver}.
|
|
329
|
+
* @returns A negative number if `a` is less than `b`, zero if they are equal, or a positive number if `a` is greater than `b`.
|
|
330
|
+
*/
|
|
331
|
+
readonly versionComparer?: (a: string, b: string) => number;
|
|
332
|
+
|
|
275
333
|
/**
|
|
276
334
|
* When true, every version must be snapshotted, and an increased version number will require a new snapshot.
|
|
277
335
|
* @remarks
|
|
@@ -280,26 +338,55 @@ export interface SchemaCompatibilitySnapshotsOptions {
|
|
|
280
338
|
* can refer to versions between snapshots and will get its schema from the preceding version.
|
|
281
339
|
*/
|
|
282
340
|
readonly snapshotUnchangedVersions?: true;
|
|
341
|
+
|
|
283
342
|
/**
|
|
284
|
-
*
|
|
343
|
+
* When true, it is an error if a new a snapshot for a new version would be created, but the schema compatibility is identical to the previous snapshot.
|
|
285
344
|
* @remarks
|
|
286
|
-
*
|
|
287
|
-
* Note: {@link SchemaCompatibilitySnapshotsOptions.snapshotUnchangedVersions} impacts this behavior.
|
|
345
|
+
* This prevents creating a snapshot with the same schema compatibility results as the previous one.
|
|
288
346
|
*
|
|
289
|
-
*
|
|
347
|
+
* Applications and libraries which do not have versioned releases can make up a version specific to the compatibility of the schema, and use this option to help ensure they manage that version correctly.
|
|
348
|
+
* Such cases can also opt into {@link SnapshotSchemaCompatibilityOptions.rejectSchemaChangesWithNoVersionChange} if they want additional strictness.
|
|
349
|
+
*/
|
|
350
|
+
readonly rejectVersionsWithNoSchemaChange?: true;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* When true, it is an error if a schema change occurs without a corresponding version change.
|
|
354
|
+
* @remarks
|
|
355
|
+
* This disables overwriting existing snapshots.
|
|
356
|
+
* This option is recommended if the {@link SnapshotSchemaCompatibilityOptions.version} is not automatically updated ahead of releasing a version which must be supported.
|
|
357
|
+
* If updating the snapshot is still desired, the preceding one which needs to be overwritten can be manually deleted before running the update.
|
|
290
358
|
*
|
|
359
|
+
* This option does not impact the behavior of assert mode (other than impacting what error is given).
|
|
360
|
+
* This option simply makes update mode more strict, converting cases that would overwrite a snapshot in place into errors.
|
|
361
|
+
*/
|
|
362
|
+
readonly rejectSchemaChangesWithNoVersionChange?: true;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The mode of operation, either "assert" or "update".
|
|
366
|
+
* @remarks
|
|
291
367
|
* Both modes will throw errors if any compatibility issues are detected (but after updating snapshots in "update" mode so the diff can be used to help debug).
|
|
292
368
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
369
|
+
* In "assert" mode, an error is additionally thrown if the latest snapshot is not up to date (meaning "update" mode would make a change).
|
|
370
|
+
*
|
|
371
|
+
* In "update" mode, a new snapshot is created if the current schema differs from the latest existing snapshot.
|
|
372
|
+
* If {@link SnapshotSchemaCompatibilityOptions.rejectVersionsWithNoSchemaChange} or
|
|
373
|
+
* {@link SnapshotSchemaCompatibilityOptions.rejectSchemaChangesWithNoVersionChange} disallows the update, an error is thrown instead.
|
|
374
|
+
*
|
|
375
|
+
* It is recommended that "assert" mode be used in automated tests to verify schema compatibility,
|
|
376
|
+
* and "update" mode only be used manually to update snapshots when making schema or version changes.
|
|
377
|
+
*
|
|
378
|
+
* @privateRemarks
|
|
379
|
+
* Modes we might want to add in the future:
|
|
380
|
+
* - normalize: update the latest snapshot (or maybe all of them) to the latest encoded format.
|
|
381
|
+
* - some mode like assert but returns information instead of throwing.
|
|
295
382
|
*/
|
|
296
|
-
readonly mode: "
|
|
383
|
+
readonly mode: "assert" | "update";
|
|
297
384
|
}
|
|
298
385
|
|
|
299
386
|
/**
|
|
300
387
|
* Check `currentViewSchema` for compatibility with a collection of historical schema snapshots stored in `snapshotDirectory`.
|
|
301
388
|
*
|
|
302
|
-
* @throws Throws errors if the input version strings (including those in snapshot file names) are not valid semver versions.
|
|
389
|
+
* @throws Throws errors if the input version strings (including those in snapshot file names) are not valid semver versions when using default semver version comparison.
|
|
303
390
|
* @throws Throws errors if the input version strings (including those in snapshot file names) are not ordered as expected (current being the highest, and `minVersionForCollaboration` corresponding to the current version or a lower snapshotted version).
|
|
304
391
|
* @throws In `test` mode, throws an error if there is not an up to date snapshot for the current version.
|
|
305
392
|
* @throws Throws an error if any snapshotted schema cannot be upgraded to the current schema.
|
|
@@ -319,6 +406,14 @@ export interface SchemaCompatibilitySnapshotsOptions {
|
|
|
319
406
|
* This is a known limitation that will be improved in future releases.
|
|
320
407
|
* These improvements, as well as other changes, may change the exact messages produced by this function in the error cases: no stability of these messages should be assumed.
|
|
321
408
|
*
|
|
409
|
+
* Unlike some other snapshot based testing tools, this stores more than just the current snapshot: historical snapshots are retained as well.
|
|
410
|
+
* Retention of these additional historical snapshots, whose content can't be regenerated from the current schema, is necessary to properly test compatibility across versions.
|
|
411
|
+
* Since there is content in the snapshots which cannot be regenerated, tools which assume all snapshotted content can be regenerated cannot be used here.
|
|
412
|
+
* This means that tools like Jest's built in snapshot testing are not suitable for this purpose.
|
|
413
|
+
* These snapshots behave partly like test data, and partly like snapshots.
|
|
414
|
+
* Typically the easiest way to manage this is to place {@link SnapshotSchemaCompatibilityOptions.snapshotDirectory} inside a directory appropriate for test data,
|
|
415
|
+
* and use node to provide the filesystem access via {@link SnapshotSchemaCompatibilityOptions.fileSystem}.
|
|
416
|
+
*
|
|
322
417
|
* For now, locating what change broke compatibility is likely best discovered by making small schema changes one at a time and updating the snapshot and reviewing the diffs.
|
|
323
418
|
* Details for what kinds of changes are breaking and in which ways can be found in the documentation for {@link TreeView.compatibility} and
|
|
324
419
|
* {@link https://fluidframework.com/docs/data-structures/tree/schema-evolution/ | schema-evolution}.
|
|
@@ -337,7 +432,7 @@ export interface SchemaCompatibilitySnapshotsOptions {
|
|
|
337
432
|
* @example Mocha test which validates the current `config` can collaborate with all historical version back to 2.0.0, and load and update any versions older than that.
|
|
338
433
|
* ```typescript
|
|
339
434
|
* it("schema compatibility", () => {
|
|
340
|
-
*
|
|
435
|
+
* snapshotSchemaCompatibility({
|
|
341
436
|
* version: pkgVersion,
|
|
342
437
|
* schema: config,
|
|
343
438
|
* fileSystem: { ...fs, ...path },
|
|
@@ -347,19 +442,53 @@ export interface SchemaCompatibilitySnapshotsOptions {
|
|
|
347
442
|
* });
|
|
348
443
|
* });
|
|
349
444
|
* ```
|
|
445
|
+
* @example Complete Mocha test file
|
|
446
|
+
* ```typescript
|
|
447
|
+
* import fs from "node:fs";
|
|
448
|
+
* import path from "node:path";
|
|
449
|
+
*
|
|
450
|
+
* import { snapshotSchemaCompatibility } from "@fluidframework/tree/alpha";
|
|
451
|
+
*
|
|
452
|
+
* // The TreeViewConfiguration the application uses, which contains the application's schema.
|
|
453
|
+
* import { treeViewConfiguration } from "./schema.js";
|
|
454
|
+
* // The next version of the application which will be released.
|
|
455
|
+
* import { packageVersion } from "./version.js";
|
|
456
|
+
*
|
|
457
|
+
* // Provide some way to run the check in "update" mode when updating snapshots is intended.
|
|
458
|
+
* const regenerateSnapshots = process.argv.includes("--snapshot");
|
|
459
|
+
*
|
|
460
|
+
* // Setup the actual test. In this case using Mocha syntax.
|
|
461
|
+
* describe("schema", () => {
|
|
462
|
+
* it("schema compatibility", () => {
|
|
463
|
+
* // Select a path to save the snapshots in.
|
|
464
|
+
* // This will depend on how your application organizes its test data.
|
|
465
|
+
* const snapshotDirectory = path.join(
|
|
466
|
+
* import.meta.dirname,
|
|
467
|
+
* "../../../src/test/schema-snapshots",
|
|
468
|
+
* );
|
|
469
|
+
* snapshotSchemaCompatibility({
|
|
470
|
+
* schema: config,
|
|
471
|
+
* fileSystem: { ...fs, ...path },
|
|
472
|
+
* version: pkgVersion,
|
|
473
|
+
* minVersionForCollaboration: "2.0.0",
|
|
474
|
+
* mode: process.argv.includes("--snapshot") ? "update" : "assert",
|
|
475
|
+
* snapshotDirectory,
|
|
476
|
+
* });
|
|
477
|
+
* });
|
|
478
|
+
* });
|
|
479
|
+
* ```
|
|
350
480
|
* @privateRemarks
|
|
351
481
|
* Use of this function within this package (for schema libraries released as part of this package) should use {@link testSchemaCompatibilitySnapshots} instead.
|
|
352
482
|
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* This should be addressed before this reached beta stability.
|
|
483
|
+
* This uses the format defined in simpleSchemaCodec.ts.
|
|
484
|
+
* This does include versioning information in the snapshot format,
|
|
485
|
+
* but it would be nice to better unify how we do that versioning and format validation with our codecs.
|
|
486
|
+
*
|
|
487
|
+
* See snapshotCompatibilityChecker.example.mts for the large example included above.
|
|
359
488
|
* @alpha
|
|
360
489
|
*/
|
|
361
|
-
export function
|
|
362
|
-
options:
|
|
490
|
+
export function snapshotSchemaCompatibility(
|
|
491
|
+
options: SnapshotSchemaCompatibilityOptions,
|
|
363
492
|
): void {
|
|
364
493
|
const checker = new SnapshotCompatibilityChecker(
|
|
365
494
|
options.snapshotDirectory,
|
|
@@ -371,123 +500,191 @@ export function checkSchemaCompatibilitySnapshots(
|
|
|
371
500
|
mode,
|
|
372
501
|
minVersionForCollaboration,
|
|
373
502
|
snapshotUnchangedVersions,
|
|
503
|
+
rejectVersionsWithNoSchemaChange,
|
|
504
|
+
rejectSchemaChangesWithNoVersionChange,
|
|
374
505
|
} = options;
|
|
375
|
-
|
|
506
|
+
|
|
507
|
+
const validateVersion =
|
|
508
|
+
options.versionComparer === undefined ? semver.valid : (v: string) => v;
|
|
509
|
+
const versionComparer = options.versionComparer ?? semver.compare;
|
|
510
|
+
|
|
511
|
+
if (validateVersion(currentVersion) === null) {
|
|
376
512
|
throw new UsageError(
|
|
377
513
|
`Invalid version: ${JSON.stringify(currentVersion)}. Must be a valid semver version.`,
|
|
378
514
|
);
|
|
379
515
|
}
|
|
380
|
-
if (
|
|
516
|
+
if (validateVersion(minVersionForCollaboration) === null) {
|
|
381
517
|
throw new UsageError(
|
|
382
518
|
`Invalid minVersionForCollaboration: ${JSON.stringify(minVersionForCollaboration)}. Must be a valid semver version.`,
|
|
383
519
|
);
|
|
384
520
|
}
|
|
385
|
-
|
|
521
|
+
|
|
522
|
+
if (versionComparer(minVersionForCollaboration, currentVersion) > 0) {
|
|
386
523
|
throw new UsageError(
|
|
387
524
|
`Invalid minVersionForCollaboration: ${JSON.stringify(minVersionForCollaboration)}. Must be less than or equal to current version ${JSON.stringify(currentVersion)}.`,
|
|
388
525
|
);
|
|
389
526
|
}
|
|
390
527
|
|
|
391
|
-
if (mode !== "
|
|
528
|
+
if (mode !== "assert" && mode !== "update") {
|
|
392
529
|
throw new UsageError(
|
|
393
|
-
`Invalid mode: ${JSON.stringify(mode)}. Must be either "
|
|
530
|
+
`Invalid mode: ${JSON.stringify(mode)}. Must be either "assert" or "update".`,
|
|
394
531
|
);
|
|
395
532
|
}
|
|
396
533
|
|
|
397
534
|
const currentEncodedForSnapshotting = exportCompatibilitySchemaSnapshot(currentViewSchema);
|
|
398
|
-
const snapshots = checker.readAllSchemaSnapshots();
|
|
535
|
+
const snapshots = checker.readAllSchemaSnapshots(versionComparer);
|
|
399
536
|
|
|
400
537
|
const compatibilityErrors: string[] = [];
|
|
401
538
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
539
|
+
const contextNotes: string[] = [];
|
|
540
|
+
|
|
541
|
+
function errorWithContext(message: string): Error {
|
|
542
|
+
return new Error(
|
|
543
|
+
[
|
|
544
|
+
"Schema compatibility check failed:",
|
|
545
|
+
message,
|
|
546
|
+
`Snapshots in: ${JSON.stringify(options.snapshotDirectory)}`,
|
|
547
|
+
`Snapshots exist for versions: ${JSON.stringify([...snapshots.keys()], undefined, "\t")}.`,
|
|
548
|
+
...contextNotes,
|
|
549
|
+
].join("\n"),
|
|
406
550
|
);
|
|
407
551
|
}
|
|
408
552
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
553
|
+
const compatibilityMap = transformMapValues(snapshots, (snapshot) =>
|
|
554
|
+
getCompatibility(currentViewSchema, snapshot),
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
// Either:
|
|
558
|
+
// - false: no update needed
|
|
559
|
+
// - the updateError message (update in update mode, error otherwise)
|
|
560
|
+
// - an error if the update is disallowed by the flags
|
|
561
|
+
let wouldUpdate: false | string | Error;
|
|
562
|
+
|
|
563
|
+
// Set wouldUpdate
|
|
564
|
+
{
|
|
565
|
+
const latestSnapshot = [...snapshots][snapshots.size - 1];
|
|
566
|
+
if (latestSnapshot === undefined) {
|
|
567
|
+
wouldUpdate = `No snapshots found.`;
|
|
413
568
|
} else {
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
569
|
+
const latestCompatibility =
|
|
570
|
+
compatibilityMap.get(latestSnapshot[0]) ??
|
|
571
|
+
fail(0xcd1 /* missing compatibilityMap entry */);
|
|
572
|
+
|
|
573
|
+
const schemaChange = !latestCompatibility.identicalCompatibility;
|
|
574
|
+
const versionChange = versionComparer(latestSnapshot[0], currentVersion) !== 0;
|
|
575
|
+
|
|
576
|
+
if (rejectVersionsWithNoSchemaChange === true && versionChange && !schemaChange) {
|
|
577
|
+
wouldUpdate = errorWithContext(
|
|
578
|
+
`Rejecting version change (${JSON.stringify(latestSnapshot[0])} to ${JSON.stringify(currentVersion)}) due to rejectVersionsWithNoSchemaChange being set.`,
|
|
418
579
|
);
|
|
419
580
|
} else if (
|
|
420
|
-
|
|
421
|
-
|
|
581
|
+
rejectSchemaChangesWithNoVersionChange === true &&
|
|
582
|
+
schemaChange &&
|
|
583
|
+
!versionChange
|
|
422
584
|
) {
|
|
423
|
-
|
|
424
|
-
`
|
|
585
|
+
wouldUpdate = errorWithContext(
|
|
586
|
+
`Rejecting schema change without version change due to existing non-equivalent snapshot for version (${JSON.stringify(latestSnapshot[0])} due to rejectSchemaChangesWithNoVersionChange being set.`,
|
|
425
587
|
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
588
|
+
} else if (snapshotUnchangedVersions === true) {
|
|
589
|
+
const currentRead = snapshots.get(currentVersion);
|
|
590
|
+
if (currentRead === undefined) {
|
|
591
|
+
wouldUpdate = `No snapshot found for version ${JSON.stringify(currentVersion)}: snapshotUnchangedVersions is true, so every version must be snapshotted.`;
|
|
592
|
+
} else if (
|
|
593
|
+
JSON.stringify(exportCompatibilitySchemaSnapshot(currentRead)) ===
|
|
594
|
+
JSON.stringify(currentEncodedForSnapshotting)
|
|
595
|
+
) {
|
|
596
|
+
wouldUpdate = false;
|
|
597
|
+
} else {
|
|
598
|
+
wouldUpdate = `Snapshot for current version ${JSON.stringify(currentVersion)} is out of date.`;
|
|
599
|
+
}
|
|
435
600
|
} else {
|
|
436
|
-
|
|
601
|
+
if (versionComparer(latestSnapshot[0], currentVersion) <= 0) {
|
|
602
|
+
wouldUpdate = schemaChange
|
|
603
|
+
? `Snapshot for current version ${JSON.stringify(currentVersion)} is out of date: schema has changed since latest existing snapshot version ${JSON.stringify(latestSnapshot[0])}.`
|
|
604
|
+
: false;
|
|
605
|
+
} else {
|
|
606
|
+
wouldUpdate = errorWithContext(
|
|
607
|
+
`Current version ${JSON.stringify(currentVersion)} is less than latest existing snapshot version ${JSON.stringify(latestSnapshot[0])}: version is expected to increase monotonically.`,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
437
610
|
}
|
|
438
|
-
|
|
439
|
-
if (
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
updatableError(
|
|
450
|
-
`Snapshot for current version ${JSON.stringify(currentVersion)} is out of date: schema has changed since latest existing snapshot version ${JSON.stringify(latestSnapshot[0])}.`,
|
|
451
|
-
);
|
|
452
|
-
}
|
|
611
|
+
|
|
612
|
+
if (!schemaChange && (snapshotUnchangedVersions !== true || !versionChange)) {
|
|
613
|
+
// eslint-disable-next-line unicorn/no-lonely-if
|
|
614
|
+
if (
|
|
615
|
+
JSON.stringify(exportCompatibilitySchemaSnapshot(latestSnapshot[1])) !==
|
|
616
|
+
JSON.stringify(currentEncodedForSnapshotting)
|
|
617
|
+
) {
|
|
618
|
+
// Schema are compatibility wise equivalent, but differ in some way (excluding json formatting).
|
|
619
|
+
// TODO: add a "normalize" mode, which do an update only in this case (or maybe even normalize json formatting as well and just always rewrite when !schemaChange)
|
|
620
|
+
// This would be useful to minimize diffs from future schema changes.
|
|
621
|
+
// This would be particularly useful if adding a second version of the format used in the snapshots.
|
|
453
622
|
}
|
|
454
|
-
} else {
|
|
455
|
-
throw new UsageError(
|
|
456
|
-
`Current version ${JSON.stringify(currentVersion)} is less than latest existing snapshot version ${JSON.stringify(latestSnapshot[0])}: version is expected to increase monotonically.`,
|
|
457
|
-
);
|
|
458
623
|
}
|
|
459
624
|
}
|
|
460
625
|
}
|
|
461
626
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (minSnapshot === undefined) {
|
|
473
|
-
compatibilityErrors.push(
|
|
474
|
-
`Using snapshotUnchangedVersions: a snapshot of the exact minVersionForCollaboration ${JSON.stringify(minVersionForCollaboration)} is required. No snapshot found.`,
|
|
627
|
+
if (wouldUpdate !== false) {
|
|
628
|
+
if (wouldUpdate instanceof Error) {
|
|
629
|
+
throw wouldUpdate;
|
|
630
|
+
}
|
|
631
|
+
if (mode === "update") {
|
|
632
|
+
checker.writeSchemaSnapshot(currentVersion, currentEncodedForSnapshotting);
|
|
633
|
+
// Update so errors below will reflect the new snapshot.
|
|
634
|
+
compatibilityMap.set(
|
|
635
|
+
currentVersion,
|
|
636
|
+
getCompatibility(currentViewSchema, currentViewSchema),
|
|
475
637
|
);
|
|
476
638
|
} else {
|
|
477
|
-
selectedMinVersionForCollaborationSnapshot = [minVersionForCollaboration, minSnapshot];
|
|
478
|
-
}
|
|
479
|
-
} else {
|
|
480
|
-
selectedMinVersionForCollaborationSnapshot = selectVersionRoundedDown(
|
|
481
|
-
minVersionForCollaboration,
|
|
482
|
-
compatibilityMap,
|
|
483
|
-
);
|
|
484
|
-
if (selectedMinVersionForCollaborationSnapshot === undefined) {
|
|
485
639
|
compatibilityErrors.push(
|
|
486
|
-
|
|
640
|
+
`${wouldUpdate} If this is expected, snapshotSchemaCompatibility can be rerun in "update" mode to update or create the snapshot.`,
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
// This case could update compatibilityMap as well, but it would hide some information about how the existing snapshot might be incompatible with the proposed new one.
|
|
644
|
+
// This lost information could be annoying if the user's intention was not to edit the schema (which is what we assume in assert mode),
|
|
645
|
+
// especially once we produce more detailed error messages that can help users understand what changed in the schema.
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Add compatibilityErrors and contextNotes as needed regarding minVersionForCollaboration.
|
|
650
|
+
// This is only done when minVersionForCollaboration is not the current version to avoid extra noise in "assert" mode
|
|
651
|
+
// (which is the only case that could error when minVersionForCollaboration === currentVersion).
|
|
652
|
+
if (minVersionForCollaboration !== currentVersion) {
|
|
653
|
+
if (snapshotUnchangedVersions === true) {
|
|
654
|
+
const minSnapshot = compatibilityMap.get(minVersionForCollaboration);
|
|
655
|
+
if (minSnapshot === undefined) {
|
|
656
|
+
compatibilityErrors.push(
|
|
657
|
+
`Using snapshotUnchangedVersions: a snapshot of the exact minVersionForCollaboration ${JSON.stringify(minVersionForCollaboration)} is required. No snapshot found.`,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
} else {
|
|
661
|
+
const selectedMinVersionForCollaborationSnapshot = selectVersionRoundedDown(
|
|
662
|
+
minVersionForCollaboration,
|
|
663
|
+
compatibilityMap,
|
|
664
|
+
versionComparer,
|
|
487
665
|
);
|
|
666
|
+
if (selectedMinVersionForCollaborationSnapshot === undefined) {
|
|
667
|
+
compatibilityErrors.push(
|
|
668
|
+
`No snapshot found with version less than or equal to minVersionForCollaboration ${JSON.stringify(minVersionForCollaboration)}.`,
|
|
669
|
+
);
|
|
670
|
+
} else if (
|
|
671
|
+
selectedMinVersionForCollaborationSnapshot[0] !== minVersionForCollaboration
|
|
672
|
+
) {
|
|
673
|
+
// Add an entry to ensure that the version which spans from before until after the cutoff for collaboration is included in the compatibility checks.
|
|
674
|
+
compatibilityMap.set(
|
|
675
|
+
minVersionForCollaboration,
|
|
676
|
+
selectedMinVersionForCollaborationSnapshot[1],
|
|
677
|
+
);
|
|
678
|
+
contextNotes.push(
|
|
679
|
+
`Due to snapshotUnchangedVersions being false and minVersionForCollaboration (${JSON.stringify(minVersionForCollaboration)}) not having an exact snapshot, the last snapshot before that version (which is ${JSON.stringify(
|
|
680
|
+
selectedMinVersionForCollaborationSnapshot[0],
|
|
681
|
+
)}) is being also being checked as if it is version ${JSON.stringify(minVersionForCollaboration)}.`,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
488
684
|
}
|
|
489
685
|
}
|
|
490
686
|
|
|
687
|
+
// Compare all snapshots against the current schema, using the compatibilityMap.
|
|
491
688
|
for (const [snapshotVersion, compatibility] of compatibilityMap) {
|
|
492
689
|
// Current should be able to view all versions.
|
|
493
690
|
if (!compatibility.currentViewOfSnapshotDocument.canUpgrade) {
|
|
@@ -496,56 +693,41 @@ export function checkSchemaCompatibilitySnapshots(
|
|
|
496
693
|
);
|
|
497
694
|
}
|
|
498
695
|
|
|
499
|
-
|
|
696
|
+
const versionComparisonToCurrent = versionComparer(snapshotVersion, currentVersion);
|
|
697
|
+
if (versionComparisonToCurrent === 0) {
|
|
500
698
|
if (currentVersion !== snapshotVersion) {
|
|
501
|
-
throw
|
|
699
|
+
throw errorWithContext(
|
|
502
700
|
`Snapshot version ${JSON.stringify(snapshotVersion)} is semantically equal but not string equal to current version ${JSON.stringify(currentVersion)}: this is not supported.`,
|
|
503
701
|
);
|
|
504
702
|
}
|
|
505
|
-
if (
|
|
506
|
-
compatibility.currentViewOfSnapshotDocument.isEquivalent === false ||
|
|
507
|
-
compatibility.snapshotViewOfCurrentDocument.isEquivalent === false
|
|
508
|
-
) {
|
|
509
|
-
compatibilityErrors.push(
|
|
510
|
-
`Current version ${JSON.stringify(snapshotVersion)} expected to be equivalent to its snapshot.`,
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
} else if (semver.lt(snapshotVersion, currentVersion)) {
|
|
514
|
-
if (selectedMinVersionForCollaborationSnapshot === undefined) {
|
|
703
|
+
if (compatibility.identicalCompatibility === false) {
|
|
515
704
|
assert(
|
|
516
|
-
|
|
517
|
-
|
|
705
|
+
wouldUpdate !== false,
|
|
706
|
+
0xcd2 /* there should have been an error for the snapshot being out of date */,
|
|
518
707
|
);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
: `${message} The specified minVersionForCollaboration is ${JSON.stringify(minVersionForCollaboration)} which was rounded down to an existing snapshot.`,
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
} else {
|
|
532
|
-
// This is the case where the historical version is less than the minimum version for collaboration.
|
|
533
|
-
// No additional validation is needed here currently, since forwards document compat from these versions is already tested above (since it applies to all snapshots).
|
|
708
|
+
}
|
|
709
|
+
} else if (versionComparisonToCurrent < 0) {
|
|
710
|
+
// Collaboration with this version is expected to work.
|
|
711
|
+
if (versionComparer(snapshotVersion, minVersionForCollaboration) >= 0) {
|
|
712
|
+
// Check that the historical version can view documents from the current version, since collaboration with this one is expected to work.
|
|
713
|
+
if (!compatibility.snapshotViewOfCurrentDocument.canView) {
|
|
714
|
+
compatibilityErrors.push(
|
|
715
|
+
`Historical version ${JSON.stringify(snapshotVersion)} cannot view documents from ${JSON.stringify(currentVersion)}: these versions are expected to be able to collaborate due to the selected minVersionForCollaboration ${JSON.stringify(minVersionForCollaboration)}.`,
|
|
716
|
+
);
|
|
534
717
|
}
|
|
718
|
+
} else {
|
|
719
|
+
// This is the case where the historical version is less than the minimum version for collaboration.
|
|
720
|
+
// No additional validation is needed here currently, since forwards document compat from these versions is already tested above (since it applies to all snapshots).
|
|
535
721
|
}
|
|
536
722
|
} else {
|
|
537
|
-
|
|
538
|
-
`
|
|
723
|
+
compatibilityErrors.push(
|
|
724
|
+
`Snapshot exists for version ${JSON.stringify(snapshotVersion)} which is greater than the current version ${JSON.stringify(currentVersion)}. This is not supported.`,
|
|
539
725
|
);
|
|
540
726
|
}
|
|
541
727
|
}
|
|
542
728
|
|
|
543
729
|
if (compatibilityErrors.length > 0) {
|
|
544
|
-
throw
|
|
545
|
-
`Schema compatibility check failed:\n${compatibilityErrors
|
|
546
|
-
.map((e) => ` - ${e}`)
|
|
547
|
-
.join("\n")}`,
|
|
548
|
-
);
|
|
730
|
+
throw errorWithContext(compatibilityErrors.map((e) => ` - ${e}`).join("\n"));
|
|
549
731
|
}
|
|
550
732
|
}
|
|
551
733
|
|
|
@@ -570,7 +752,7 @@ export class SnapshotCompatibilityChecker {
|
|
|
570
752
|
`${snapshotName}.json`,
|
|
571
753
|
);
|
|
572
754
|
this.ensureSnapshotDirectoryExists();
|
|
573
|
-
this.fileSystemMethods.writeFileSync(fullPath, JSON.stringify(snapshot, undefined,
|
|
755
|
+
this.fileSystemMethods.writeFileSync(fullPath, JSON.stringify(snapshot, undefined, "\t"), {
|
|
574
756
|
encoding: "utf8",
|
|
575
757
|
});
|
|
576
758
|
}
|
|
@@ -594,7 +776,9 @@ export class SnapshotCompatibilityChecker {
|
|
|
594
776
|
/**
|
|
595
777
|
* Returns all schema snapshots stored in the snapshot directory, sorted in order of increasing version.
|
|
596
778
|
*/
|
|
597
|
-
public readAllSchemaSnapshots(
|
|
779
|
+
public readAllSchemaSnapshots(
|
|
780
|
+
compare: (a: string, b: string) => number,
|
|
781
|
+
): Map<string, TreeViewConfiguration> {
|
|
598
782
|
this.ensureSnapshotDirectoryExists();
|
|
599
783
|
const files = this.fileSystemMethods.readdirSync(this.snapshotDirectory);
|
|
600
784
|
const versions: string[] = [];
|
|
@@ -605,7 +789,7 @@ export class SnapshotCompatibilityChecker {
|
|
|
605
789
|
}
|
|
606
790
|
}
|
|
607
791
|
// Ensures that errors are in a consistent and friendly order, independent of file system order.
|
|
608
|
-
versions.sort(
|
|
792
|
+
versions.sort(compare);
|
|
609
793
|
|
|
610
794
|
const snapshots: Map<string, TreeViewConfiguration> = new Map();
|
|
611
795
|
for (const version of versions) {
|
|
@@ -639,8 +823,26 @@ export function getCompatibility(
|
|
|
639
823
|
previousViewSchema,
|
|
640
824
|
);
|
|
641
825
|
|
|
826
|
+
assert(
|
|
827
|
+
backwardsCompatibilityStatus.isEquivalent === forwardsCompatibilityStatus.isEquivalent,
|
|
828
|
+
0xcd3 /* equality should be symmetric */,
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
// This relies on exportCompatibilitySchemaSnapshot being well normalized, and not differing for non-significant changes.
|
|
832
|
+
const identicalCompatibility =
|
|
833
|
+
JSON.stringify(exportCompatibilitySchemaSnapshot(currentViewSchema)) ===
|
|
834
|
+
JSON.stringify(exportCompatibilitySchemaSnapshot(previousViewSchema));
|
|
835
|
+
|
|
836
|
+
if (identicalCompatibility) {
|
|
837
|
+
assert(
|
|
838
|
+
backwardsCompatibilityStatus.isEquivalent,
|
|
839
|
+
0xcd4 /* identicalCompatibility should have equivalent stored schema */,
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
642
843
|
return {
|
|
643
844
|
currentViewOfSnapshotDocument: backwardsCompatibilityStatus,
|
|
644
845
|
snapshotViewOfCurrentDocument: forwardsCompatibilityStatus,
|
|
846
|
+
identicalCompatibility,
|
|
645
847
|
};
|
|
646
848
|
}
|