Skip to content

Commit 7f59e31

Browse files
TommyBrosmanalexvy86CraigMacomber
authored
Shared Tree: Use minVersionForCollab instead of oldestCompatibleClient to specify min client version (#25402)
## Description This change replaces `SharedTreeOptions.oldestCompatibleClient` with `minVersionForCollab`. Additionally, Shared Tree will now use the `minVersionForCollab` specified at the ContainerRuntime level when `minVersionForCollab` is not specified at the Shared Tree level. Changes APIs and codec code to use MinimumVersionForCollab in place of FluidClientVersion. Also changes FluidClientVersion to a const object that contains MinimumVersionForCollab values. ## Breaking Changes This change breaks the following alpha APIs: - `SharedTreeOptions`: `oldestCompatibleClient` has been replaced with `minVersionForCollab`. See changeset notes on how to specify the minimum compatible version. - `CodecWriteOptions.oldestCompatibleClient` (type change) - `makeDetachedFieldIndexCodec` (parameter type change) - `TreeAlpha.exportCompressed` (parameter type change) - `extractPersistedSchema` (parameter type change) --------- Co-authored-by: Alex Villarreal <[email protected]> Co-authored-by: Craig Macomber (Microsoft) <[email protected]>
1 parent 89b3e35 commit 7f59e31

File tree

28 files changed

+143
-105
lines changed

28 files changed

+143
-105
lines changed

.changeset/warm-windows-cross.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"@fluidframework/tree": minor
3+
"fluid-framework": minor
4+
"__section": tree
5+
---
6+
MinimumVersionForCollab is now used in place of tree's alpha FluidClientVersion
7+
8+
`FluidClientVersion`: No longer used as the type for Fluid Client versions in APIs/codecs (for example, `oldestCompatibleClient`).
9+
Additionally, `FluidClientVersion` is now a const object with members that declare specific [`MinimumVersionForCollab`](https://fluidframework.com/docs/api/runtime-definitions/minimumversionforcollab-typealias) versions.
10+
These are intended to be used with APIs that require a version (such as `TreeAlpha.exportCompressed`).
11+
12+
`CodecWriteOptions` and `SharedTreeOptions`: `oldestCompatibleClient` has been replaced by `minVersionForCollab`.
13+
See migration guide below.
14+
15+
`TreeAlpha.exportCompressed`: The `options` parameter previously had `oldestCompatibleClient` and now has `minVersionForCollab`.
16+
Migrating requires a rename. Existing `FluidClientVersion.*` values are now `MinimumClientVersion`s.
17+
18+
#### Migrating
19+
20+
If an application is calling `loadContainerRuntime` directly and previously specified the minimum client version when
21+
initializing Shared Tree like:
22+
23+
```ts
24+
const factory = configuredSharedTree({ ..., oldestCompatibleClient: FluidClientVersion.v2_52 });
25+
```
26+
27+
Then the new implementation depends on how the application initializes Fluid.
28+
29+
##### Applications using `AzureClient`/`OdspClient`
30+
31+
If an application is using the declarative model (for example, `AzureClient`/`OdspClient`), it should continue to call `configuredSharedTree`
32+
but specify `minVersionForCollab` instead:
33+
34+
```ts
35+
const factory = configuredSharedTree({ ..., minVersionForCollab: "2.52.0" });
36+
```
37+
38+
##### Applications calling `loadContainerRuntime`
39+
40+
If an application is initializing the `ContainerRuntime` directly, it should now specify the `minVersionForCollab` there:
41+
42+
```ts
43+
const runtime = await loadContainerRuntime({ ..., minVersionForCollab: "2.52.0" });
44+
```

examples/apps/tree-cli-app/src/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,15 @@ export function exportContent(destination: string, tree: List): JsonCompatible {
159159
case "compressed": {
160160
return TreeAlpha.exportCompressed(tree, {
161161
...options,
162-
oldestCompatibleClient: compatVersion,
162+
minVersionForCollab: compatVersion,
163163
}) as JsonCompatible;
164164
}
165165
case "snapshot": {
166166
// TODO: This should be made better. See privateRemarks on TreeAlpha.exportCompressed.
167167
const idCompressor = createIdCompressor();
168168
const file: File = {
169169
tree: TreeAlpha.exportCompressed(tree, {
170-
oldestCompatibleClient: compatVersion,
170+
minVersionForCollab: compatVersion,
171171
idCompressor,
172172
}),
173173

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export function cloneWithReplacements(root: unknown, rootKey: string, replacer:
136136

137137
// @alpha @input
138138
export interface CodecWriteOptions extends ICodecOptions {
139-
readonly oldestCompatibleClient: FluidClientVersion;
139+
readonly minVersionForCollab: MinimumVersionForCollab;
140140
}
141141

142142
// @public
@@ -211,7 +211,7 @@ export function evaluateLazySchema<T extends TreeNodeSchema>(value: LazyItem<T>)
211211
type ExtractItemType<Item extends LazyItem> = Item extends () => infer Result ? Result : Item;
212212

213213
// @alpha
214-
export function extractPersistedSchema(schema: ImplicitFieldSchema, oldestCompatibleClient: FluidClientVersion, includeStaged: (upgrade: SchemaUpgrade) => boolean): JsonCompatible;
214+
export function extractPersistedSchema(schema: ImplicitFieldSchema, minVersionForCollab: MinimumVersionForCollab, includeStaged: (upgrade: SchemaUpgrade) => boolean): JsonCompatible;
215215

216216
// @alpha @system
217217
export type FactoryContent = IFluidHandle | string | number | boolean | null | Iterable<readonly [string, InsertableContent]> | readonly InsertableContent[] | FactoryContentObject;
@@ -296,11 +296,10 @@ type FlexList<Item = unknown> = readonly LazyItem<Item>[];
296296
type FlexListToUnion<TList extends FlexList> = ExtractItemType<TList[number]>;
297297

298298
// @alpha
299-
export enum FluidClientVersion {
300-
EnableUnstableFeatures,
301-
v2_0 = 2,
302-
v2_52 = 2.052
303-
}
299+
export const FluidClientVersion: {
300+
readonly v2_0: "2.0.0";
301+
readonly v2_52: "2.52.0";
302+
};
304303

305304
// @alpha
306305
export namespace FluidSerializableAsTree {
@@ -1347,7 +1346,7 @@ export interface TreeAlpha {
13471346
create<const TSchema extends ImplicitFieldSchema | UnsafeUnknownSchema>(schema: UnsafeUnknownSchema extends TSchema ? ImplicitFieldSchema : TSchema & ImplicitFieldSchema, data: InsertableField<TSchema>): Unhydrated<TSchema extends ImplicitFieldSchema ? TreeFieldFromImplicitField<TSchema> : TreeNode | TreeLeafValue | undefined>;
13481347
exportCompressed(tree: TreeNode | TreeLeafValue, options: {
13491348
idCompressor?: IIdCompressor;
1350-
} & Pick<CodecWriteOptions, "oldestCompatibleClient">): JsonCompatible<IFluidHandle>;
1349+
} & Pick<CodecWriteOptions, "minVersionForCollab">): JsonCompatible<IFluidHandle>;
13511350
exportConcise(node: TreeNode | TreeLeafValue, options?: TreeEncodingOptions): ConciseTree;
13521351
exportConcise(node: TreeNode | TreeLeafValue | undefined, options?: TreeEncodingOptions): ConciseTree | undefined;
13531352
exportVerbose(node: TreeNode | TreeLeafValue, options?: TreeEncodingOptions): VerboseTree;

packages/dds/tree/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
"@tylerbu/sorted-btree-es6": "^1.8.0",
181181
"@types/ungap__structured-clone": "^1.2.0",
182182
"@ungap/structured-clone": "^1.2.0",
183+
"semver-ts": "^1.0.3",
183184
"uuid": "^11.1.0"
184185
},
185186
"devDependencies": {

packages/dds/tree/src/codec/codec.ts

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { Static, TAnySchema, TSchema } from "@sinclair/typebox";
1010

1111
import type { ChangeEncodingContext } from "../core/index.js";
1212
import type { JsonCompatibleReadOnly } from "../util/index.js";
13+
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
1314

1415
/**
1516
* Translates decoded data to encoded data.
@@ -151,7 +152,7 @@ export interface CodecWriteOptions extends ICodecOptions {
151152
* Note that versions older than this should not result in data corruption if they access the data:
152153
* the data's format should be versioned and if they can't handle the format they should error.
153154
*/
154-
readonly oldestCompatibleClient: FluidClientVersion;
155+
readonly minVersionForCollab: MinimumVersionForCollab;
155156
}
156157

157158
/**
@@ -466,16 +467,6 @@ export function withSchemaValidation<
466467
* For example, document if there is an encoding efficiency improvement of oping into that version or newer.
467468
* Versions with no notable impact can be omitted.
468469
*
469-
* These use numeric values for easy threshold comparisons.
470-
* Without zero padding, version 2.10 is treated as 2.1, which is numerically less than 2.2.
471-
* Adding leading zeros to the minor version ensures correct comparisons.
472-
* For example, version 2.20.0 is encoded as 2.020, and version 2.2.0 is encoded as 2.002.
473-
* For example FF 2.20.0 is encoded as 2.020 and FF 2.2.0 is encoded as 2.002.
474-
*
475-
* Three digits was selected as that will likely be enough, while two digits could easily be too few.
476-
* If three digits ends up being too few, minor releases of 1000 and higher
477-
* could still be handled using something like 2.999_00001 without having to change the lower releases.
478-
*
479470
* This scheme assumes a single version will always be enough to communicate compatibility.
480471
* For this to work, compatibility has to be strictly increasing.
481472
* If this is violated (for example a subset of incompatible features from 3.x that are not in 3.0 are back ported to 2.x),
@@ -487,7 +478,7 @@ export function withSchemaValidation<
487478
* For example, if needed, would adding more leading zeros to the minor version break things.
488479
* @alpha
489480
*/
490-
export enum FluidClientVersion {
481+
export const FluidClientVersion = {
491482
/**
492483
* Fluid Framework Client 1.4 and newer.
493484
* @remarks
@@ -497,8 +488,10 @@ export enum FluidClientVersion {
497488
*/
498489
// v1_4 = 1.004,
499490

500-
/** Fluid Framework Client 2.0 and newer. */
501-
v2_0 = 2.0,
491+
/**
492+
* Fluid Framework Client 2.0 and newer.
493+
*/
494+
v2_0: "2.0.0",
502495

503496
/** Fluid Framework Client 2.1 and newer. */
504497
// If we think we might want to start allowing opting into something that landed in 2.1 (without opting into something newer),
@@ -509,19 +502,8 @@ export enum FluidClientVersion {
509502
/** Fluid Framework Client 2.52 and newer. */
510503
// New formats introduced in 2.52:
511504
// - DetachedFieldIndex FormatV2
512-
v2_52 = 2.052,
513-
514-
/**
515-
* Enable unreleased and unfinished features.
516-
* @remarks
517-
* Using this value can result in documents which can not be opened in future versions of the framework.
518-
* It can also result in data corruption by enabling unfinished features which may not handle all cases correctly.
519-
*
520-
* This can be used with specific APIs when the caller has knowledge of what specific features those APIs will be opted into with it.
521-
* This is useful for testing features before they are released, but should not be used in production code.
522-
*/
523-
EnableUnstableFeatures = Number.POSITIVE_INFINITY,
524-
}
505+
v2_52: "2.52.0",
506+
} as const satisfies Record<string, MinimumVersionForCollab>;
525507

526508
/**
527509
* An up to date version which includes all the important stable features.
@@ -532,7 +514,7 @@ export enum FluidClientVersion {
532514
* Update as needed.
533515
* TODO: Consider using packageVersion.ts to keep this current.
534516
*/
535-
export const currentVersion: FluidClientVersion = FluidClientVersion.v2_0;
517+
export const currentVersion: MinimumVersionForCollab = FluidClientVersion.v2_0;
536518

537519
export interface CodecTree {
538520
readonly name: string;

packages/dds/tree/src/codec/versioned/codec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import {
1414
type IJsonCodec,
1515
withSchemaValidation,
1616
type FormatVersion,
17-
type FluidClientVersion,
1817
type CodecWriteOptions,
1918
} from "../codec.js";
2019

2120
import { Versioned } from "./format.js";
2221
import { pkgVersion } from "../../packageVersion.js";
22+
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
2323

2424
export function makeVersionedCodec<
2525
TDecoded,
@@ -45,7 +45,7 @@ export function makeVersionedCodec<
4545
if (!supportedVersions.has(versioned.version)) {
4646
throw new UsageError(
4747
`Unsupported version ${versioned.version} encountered while decoding data. Supported versions for this data are: ${Array.from(supportedVersions).join(", ")}.
48-
The client which encoded this data likely specified an "oldestCompatibleClient" value which corresponds to a version newer than the version of this client ("${pkgVersion}").`,
48+
The client which encoded this data likely specified an "minVersionForCollab" value which corresponds to a version newer than the version of this client ("${pkgVersion}").`,
4949
);
5050
}
5151
const decoded = inner.decode(data, context);
@@ -103,7 +103,7 @@ export function makeVersionDispatchingCodec<TDecoded, TContext>(
103103
}
104104

105105
/**
106-
* Creates a codec which dispatches to the appropriate member of a codec family based on the `oldestCompatibleClient` for encode and the
106+
* Creates a codec which dispatches to the appropriate member of a codec family based on the `minVersionForCollab` for encode and the
107107
* version number in data it encounters for decode.
108108
* @privateRemarks
109109
* This is a two stage builder so the first stage can encapsulate all codec specific details and the second can bring in configuration.
@@ -115,17 +115,17 @@ export class ClientVersionDispatchingCodecBuilder<TDecoded, TContext> {
115115
*/
116116
private readonly family: ICodecFamily<TDecoded, TContext>,
117117
/**
118-
* A function which maps a {@link FluidClientVersion} to a version number for the codec family which is supported by that version.
118+
* A function which maps a {@link MinimumVersionForCollab} to a version number for the codec family which is supported by that version.
119119
* This can (and typically does) pick the newest version of the codec which is known to be compatible with the client version so that
120120
* any improvements in newer versions of the codec can be used when allowed.
121121
*/
122-
private readonly versionMapping: (oldestCompatibleClient: FluidClientVersion) => number,
122+
private readonly versionMapping: (minVersionForCollab: MinimumVersionForCollab) => number,
123123
) {}
124124

125125
public build(
126126
options: CodecWriteOptions,
127127
): IJsonCodec<TDecoded, JsonCompatibleReadOnly, JsonCompatibleReadOnly, TContext> {
128-
const writeVersion = this.versionMapping(options.oldestCompatibleClient);
128+
const writeVersion = this.versionMapping(options.minVersionForCollab);
129129
return makeVersionDispatchingCodec(this.family, { ...options, writeVersion });
130130
}
131131
}

packages/dds/tree/src/core/tree/detachedFieldIndex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class DetachedFieldIndex {
8484
) {
8585
this.options = options ?? {
8686
jsonValidator: FormatValidatorNoOp,
87-
oldestCompatibleClient: FluidClientVersion.v2_0,
87+
minVersionForCollab: FluidClientVersion.v2_0,
8888
};
8989
this.codec = makeDetachedFieldIndexCodec(revisionTagCodec, this.options, idCompressor);
9090
}

packages/dds/tree/src/core/tree/detachedFieldIndexCodecs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function makeDetachedFieldIndexCodec(
3030
): IJsonCodec<DetachedFieldSummaryData> {
3131
const family = makeDetachedFieldIndexCodecFamily(revisionTagCodec, options, idCompressor);
3232
const writeVersion =
33-
options.oldestCompatibleClient < FluidClientVersion.v2_52 ? version1 : version2;
33+
options.minVersionForCollab < FluidClientVersion.v2_52 ? version1 : version2;
3434
return makeVersionDispatchingCodec(family, { ...options, writeVersion });
3535
}
3636

packages/dds/tree/src/feature-libraries/chunked-forest/codec/codecs.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type { IIdCompressor, SessionId } from "@fluidframework/id-compressor";
88

99
import {
1010
type CodecTree,
11-
type FluidClientVersion,
1211
type ICodecOptions,
1312
type IJsonCodec,
1413
makeVersionedValidatedCodec,
@@ -35,6 +34,7 @@ import type { FieldBatch } from "./fieldBatch.js";
3534
import { EncodedFieldBatch, validVersions, type FieldBatchFormatVersion } from "./format.js";
3635
import { schemaCompressedEncode } from "./schemaBasedEncode.js";
3736
import { uncompressedEncode } from "./uncompressedEncode.js";
37+
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
3838
import type { IncrementalEncodingPolicy } from "./incrementalEncodingPolicy.js";
3939

4040
/**
@@ -115,12 +115,12 @@ export type FieldBatchCodec = IJsonCodec<
115115
>;
116116

117117
/**
118-
* Get the write version for {@link makeFieldBatchCodec} based on the `oldestCompatibleClient` version.
118+
* Get the write version for {@link makeFieldBatchCodec} based on the `minVersionForCollab` version.
119119
* @privateRemarks
120120
* TODO: makeFieldBatchCodec (and makeVersionDispatchingCodec transitively) should bake in this versionToFormat logic and the resulting codec can then support use with FluidClientVersion directly.
121121
*/
122122
export function fluidVersionToFieldBatchCodecWriteVersion(
123-
oldestCompatibleClient: FluidClientVersion,
123+
minVersionForCollab: MinimumVersionForCollab,
124124
): number {
125125
// There is currently on only 1 version.
126126
return 1;

packages/dds/tree/src/feature-libraries/schema-index/codec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { fail, unreachableCase } from "@fluidframework/core-utils/internal";
77

88
import {
99
type CodecTree,
10-
type FluidClientVersion,
1110
type ICodecFamily,
1211
type ICodecOptions,
1312
type IJsonCodec,
@@ -29,14 +28,15 @@ import { brand, type JsonCompatible } from "../../util/index.js";
2928

3029
import { Format as FormatV1 } from "./formatV1.js";
3130
import { Format as FormatV2 } from "./formatV2.js";
31+
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
3232

3333
/**
34-
* Convert a FluidClientVersion to a SchemaVersion.
35-
* @param clientVersion - The FluidClientVersion to convert.
36-
* @returns The SchemaVersion that corresponds to the provided FluidClientVersion.
34+
* Convert a MinimumVersionForCollab to a SchemaVersion.
35+
* @param clientVersion - The MinimumVersionForCollab to convert.
36+
* @returns The SchemaVersion that corresponds to the provided MinimumVersionForCollab.
3737
*/
3838
export function clientVersionToSchemaVersion(
39-
clientVersion: FluidClientVersion,
39+
clientVersion: MinimumVersionForCollab,
4040
): SchemaVersion {
4141
// Only one version of the schema codec is currently supported.
4242
return SchemaVersion.v1;

0 commit comments

Comments
 (0)