Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .changeset/warm-windows-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
"@fluidframework/tree": minor
"fluid-framework": minor
"__section": tree
---
MinimumVersionForCollab is now used in place of tree's alpha FluidClientVersion

`FluidClientVersion`: No longer used as the type for Fluid Client versions in APIs/codecs (for example, `oldestCompatibleClient`).
Additionally, `FluidClientVersion` is now a const object with members that declare specific [`MinimumVersionForCollab`](https://fluidframework.com/docs/api/runtime-definitions/minimumversionforcollab-typealias) versions.

Check failure on line 9 in .changeset/warm-windows-cross.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'const'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'const'?", "location": {"path": ".changeset/warm-windows-cross.md", "range": {"start": {"line": 9, "column": 45}}}, "severity": "ERROR"}
These are intended to be used with APIs that require a version (such as `TreeAlpha.exportCompressed`).

`CodecWriteOptions` and `SharedTreeOptions`: `oldestCompatibleClient` has been replaced by `minVersionForCollab`.
See migration guide below.

`TreeAlpha.exportCompressed`: The `options` parameter previously had `oldestCompatibleClient` and now has `minVersionForCollab`.
Migrating requires a rename. Existing `FluidClientVersion.*` values are now `MinimumClientVersion`s.

#### Migrating

If an application is calling `loadContainerRuntime` directly and previously specified the minimum client version when
initializing Shared Tree like:

```ts
const factory = configuredSharedTree({ ..., oldestCompatibleClient: FluidClientVersion.v2_52 });
```

Then the new implementation depends on how the application initializes Fluid.

##### Migrating: applications using `AzureClient`/`OdspClient`

Check failure on line 29 in .changeset/warm-windows-cross.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.HeadingColons] Capitalize ': a'. Raw Output: {"message": "[Microsoft.HeadingColons] Capitalize ': a'.", "location": {"path": ".changeset/warm-windows-cross.md", "range": {"start": {"line": 29, "column": 16}}}, "severity": "ERROR"}

If an application is using the declarative model (e.g., `AzureClient`/`OdspClient`), it should continue to call `configuredSharedTree`

Check failure on line 31 in .changeset/warm-windows-cross.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.Foreign] Use 'for example' instead of 'e.g.,'. Raw Output: {"message": "[Microsoft.Foreign] Use 'for example' instead of 'e.g.,'.", "location": {"path": ".changeset/warm-windows-cross.md", "range": {"start": {"line": 31, "column": 51}}}, "severity": "ERROR"}
but specify `minVersionForCollab` instead:

```ts
const factory = configuredSharedTree({ ..., minVersionForCollab: "2.52.0" });
```

##### Migrating: applications calling `loadContainerRuntime`

Check failure on line 38 in .changeset/warm-windows-cross.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.HeadingColons] Capitalize ': a'. Raw Output: {"message": "[Microsoft.HeadingColons] Capitalize ': a'.", "location": {"path": ".changeset/warm-windows-cross.md", "range": {"start": {"line": 38, "column": 16}}}, "severity": "ERROR"}

Check warning on line 38 in .changeset/warm-windows-cross.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.Headings] 'Migrating: applications calling ********************' should use sentence-style capitalization. Raw Output: {"message": "[Microsoft.Headings] 'Migrating: applications calling ********************' should use sentence-style capitalization.", "location": {"path": ".changeset/warm-windows-cross.md", "range": {"start": {"line": 38, "column": 1}}}, "severity": "INFO"}

If an application is initializing the `ContainerRuntime` directly, it should now specify the `minVersionForCollab` there:

```ts
const runtime = await loadContainerRuntime({ ..., minVersionForCollab: "2.52.0" });
```
15 changes: 7 additions & 8 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function cloneWithReplacements(root: unknown, rootKey: string, replacer:

// @alpha @input
export interface CodecWriteOptions extends ICodecOptions {
readonly oldestCompatibleClient: FluidClientVersion;
readonly minVersionForCollab: MinimumVersionForCollab;
}

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

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

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

// @alpha
export enum FluidClientVersion {
EnableUnstableFeatures,
v2_0 = 2,
v2_52 = 2.052
}
export const FluidClientVersion: {
readonly v2_0: "2.0.0";
readonly v2_52: "2.52.0";
};

// @alpha
export namespace FluidSerializableAsTree {
Expand Down Expand Up @@ -1347,7 +1346,7 @@ export interface TreeAlpha {
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>;
exportCompressed(tree: TreeNode | TreeLeafValue, options: {
idCompressor?: IIdCompressor;
} & Pick<CodecWriteOptions, "oldestCompatibleClient">): JsonCompatible<IFluidHandle>;
} & Pick<CodecWriteOptions, "minVersionForCollab">): JsonCompatible<IFluidHandle>;
exportConcise(node: TreeNode | TreeLeafValue, options?: TreeEncodingOptions): ConciseTree;
exportConcise(node: TreeNode | TreeLeafValue | undefined, options?: TreeEncodingOptions): ConciseTree | undefined;
exportVerbose(node: TreeNode | TreeLeafValue, options?: TreeEncodingOptions): VerboseTree;
Expand Down
1 change: 1 addition & 0 deletions packages/dds/tree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
"@tylerbu/sorted-btree-es6": "^1.8.0",
"@types/ungap__structured-clone": "^1.2.0",
"@ungap/structured-clone": "^1.2.0",
"semver-ts": "^1.0.3",
"uuid": "^11.1.0"
},
"devDependencies": {
Expand Down
38 changes: 10 additions & 28 deletions packages/dds/tree/src/codec/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { Static, TAnySchema, TSchema } from "@sinclair/typebox";

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

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

/**
Expand Down Expand Up @@ -466,16 +467,6 @@ export function withSchemaValidation<
* For example, document if there is an encoding efficiency improvement of oping into that version or newer.
* Versions with no notable impact can be omitted.
*
* These use numeric values for easy threshold comparisons.
* Without zero padding, version 2.10 is treated as 2.1, which is numerically less than 2.2.
* Adding leading zeros to the minor version ensures correct comparisons.
* For example, version 2.20.0 is encoded as 2.020, and version 2.2.0 is encoded as 2.002.
* For example FF 2.20.0 is encoded as 2.020 and FF 2.2.0 is encoded as 2.002.
*
* Three digits was selected as that will likely be enough, while two digits could easily be too few.
* If three digits ends up being too few, minor releases of 1000 and higher
* could still be handled using something like 2.999_00001 without having to change the lower releases.
*
* This scheme assumes a single version will always be enough to communicate compatibility.
* For this to work, compatibility has to be strictly increasing.
* 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),
Expand All @@ -487,7 +478,7 @@ export function withSchemaValidation<
* For example, if needed, would adding more leading zeros to the minor version break things.
* @alpha
*/
export enum FluidClientVersion {
export const FluidClientVersion = {
/**
* Fluid Framework Client 1.4 and newer.
* @remarks
Expand All @@ -497,8 +488,10 @@ export enum FluidClientVersion {
*/
// v1_4 = 1.004,

/** Fluid Framework Client 2.0 and newer. */
v2_0 = 2.0,
/**
* Fluid Framework Client 2.0 and newer.
*/
v2_0: "2.0.0",

/** Fluid Framework Client 2.1 and newer. */
// If we think we might want to start allowing opting into something that landed in 2.1 (without opting into something newer),
Expand All @@ -509,19 +502,8 @@ export enum FluidClientVersion {
/** Fluid Framework Client 2.52 and newer. */
// New formats introduced in 2.52:
// - DetachedFieldIndex FormatV2
v2_52 = 2.052,

/**
* Enable unreleased and unfinished features.
* @remarks
* Using this value can result in documents which can not be opened in future versions of the framework.
* It can also result in data corruption by enabling unfinished features which may not handle all cases correctly.
*
* This can be used with specific APIs when the caller has knowledge of what specific features those APIs will be opted into with it.
* This is useful for testing features before they are released, but should not be used in production code.
*/
EnableUnstableFeatures = Number.POSITIVE_INFINITY,
}
v2_52: "2.52.0",
} as const satisfies Record<string, MinimumVersionForCollab>;

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

export interface CodecTree {
readonly name: string;
Expand Down
12 changes: 6 additions & 6 deletions packages/dds/tree/src/codec/versioned/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import {
type IJsonCodec,
withSchemaValidation,
type FormatVersion,
type FluidClientVersion,
type CodecWriteOptions,
} from "../codec.js";

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

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

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

public build(
options: CodecWriteOptions,
): IJsonCodec<TDecoded, JsonCompatibleReadOnly, JsonCompatibleReadOnly, TContext> {
const writeVersion = this.versionMapping(options.oldestCompatibleClient);
const writeVersion = this.versionMapping(options.minVersionForCollab);
return makeVersionDispatchingCodec(this.family, { ...options, writeVersion });
}
}
2 changes: 1 addition & 1 deletion packages/dds/tree/src/core/tree/detachedFieldIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class DetachedFieldIndex {
) {
this.options = options ?? {
jsonValidator: FormatValidatorNoOp,
oldestCompatibleClient: FluidClientVersion.v2_0,
minVersionForCollab: FluidClientVersion.v2_0,
};
this.codec = makeDetachedFieldIndexCodec(revisionTagCodec, this.options, idCompressor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function makeDetachedFieldIndexCodec(
): IJsonCodec<DetachedFieldSummaryData> {
const family = makeDetachedFieldIndexCodecFamily(revisionTagCodec, options, idCompressor);
const writeVersion =
options.oldestCompatibleClient < FluidClientVersion.v2_52 ? version1 : version2;
options.minVersionForCollab < FluidClientVersion.v2_52 ? version1 : version2;
return makeVersionDispatchingCodec(family, { ...options, writeVersion });
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import type { IIdCompressor, SessionId } from "@fluidframework/id-compressor";

import {
type CodecTree,
type FluidClientVersion,
type ICodecOptions,
type IJsonCodec,
makeVersionedValidatedCodec,
Expand All @@ -35,6 +34,7 @@ import type { FieldBatch } from "./fieldBatch.js";
import { EncodedFieldBatch, validVersions, type FieldBatchFormatVersion } from "./format.js";
import { schemaCompressedEncode } from "./schemaBasedEncode.js";
import { uncompressedEncode } from "./uncompressedEncode.js";
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
import type { IncrementalEncodingPolicy } from "./incrementalEncodingPolicy.js";

/**
Expand Down Expand Up @@ -115,12 +115,12 @@ export type FieldBatchCodec = IJsonCodec<
>;

/**
* Get the write version for {@link makeFieldBatchCodec} based on the `oldestCompatibleClient` version.
* Get the write version for {@link makeFieldBatchCodec} based on the `minVersionForCollab` version.
* @privateRemarks
* TODO: makeFieldBatchCodec (and makeVersionDispatchingCodec transitively) should bake in this versionToFormat logic and the resulting codec can then support use with FluidClientVersion directly.
*/
export function fluidVersionToFieldBatchCodecWriteVersion(
oldestCompatibleClient: FluidClientVersion,
minVersionForCollab: MinimumVersionForCollab,
): number {
// There is currently on only 1 version.
return 1;
Expand Down
10 changes: 5 additions & 5 deletions packages/dds/tree/src/feature-libraries/schema-index/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { fail, unreachableCase } from "@fluidframework/core-utils/internal";

import {
type CodecTree,
type FluidClientVersion,
type ICodecFamily,
type ICodecOptions,
type IJsonCodec,
Expand All @@ -29,14 +28,15 @@ import { brand, type JsonCompatible } from "../../util/index.js";

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

/**
* Convert a FluidClientVersion to a SchemaVersion.
* @param clientVersion - The FluidClientVersion to convert.
* @returns The SchemaVersion that corresponds to the provided FluidClientVersion.
* Convert a MinimumVersionForCollab to a SchemaVersion.
* @param clientVersion - The MinimumVersionForCollab to convert.
* @returns The SchemaVersion that corresponds to the provided MinimumVersionForCollab.
*/
export function clientVersionToSchemaVersion(
clientVersion: FluidClientVersion,
clientVersion: MinimumVersionForCollab,
): SchemaVersion {
// Only one version of the schema codec is currently supported.
return SchemaVersion.v1;
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/src/shared-tree/sharedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ export function buildConfiguredForest(

export const defaultSharedTreeOptions: Required<SharedTreeOptionsInternal> = {
jsonValidator: FormatValidatorNoOp,
oldestCompatibleClient: FluidClientVersion.v2_0,
minVersionForCollab: FluidClientVersion.v2_0,
forest: ForestTypeReference,
treeEncodeType: TreeCompressionStrategy.Compressed,
formatVersion: SharedTreeFormatVersion.v3,
Expand Down
12 changes: 3 additions & 9 deletions packages/dds/tree/src/shared-tree/treeAlpha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,7 @@ export interface TreeAlpha {
*/
exportCompressed(
tree: TreeNode | TreeLeafValue,
options: { idCompressor?: IIdCompressor } & Pick<
CodecWriteOptions,
"oldestCompatibleClient"
>,
options: { idCompressor?: IIdCompressor } & Pick<CodecWriteOptions, "minVersionForCollab">,
): JsonCompatible<IFluidHandle>;

/**
Expand Down Expand Up @@ -812,13 +809,10 @@ export const TreeAlpha: TreeAlpha = {

exportCompressed(
node: TreeNode | TreeLeafValue,
options: { idCompressor?: IIdCompressor } & Pick<
CodecWriteOptions,
"oldestCompatibleClient"
>,
options: { idCompressor?: IIdCompressor } & Pick<CodecWriteOptions, "minVersionForCollab">,
): JsonCompatible<IFluidHandle> {
const schema = tryGetSchema(node) ?? fail(0xacf /* invalid input */);
const format = fluidVersionToFieldBatchCodecWriteVersion(options.oldestCompatibleClient);
const format = fluidVersionToFieldBatchCodecWriteVersion(options.minVersionForCollab);
const codec = makeFieldBatchCodec({ jsonValidator: FormatValidatorNoOp }, format);
const cursor = borrowFieldCursorFromTreeNodeOrValue(node);
const batch: FieldBatch = [cursor];
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/src/shared-tree/treeCheckout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export function createTreeCheckout(
const forest = args?.forest ?? buildForest(breaker, schema);
const defaultCodecOptions = {
jsonValidator: FormatValidatorNoOp,
oldestCompatibleClient: FluidClientVersion.v2_0,
minVersionForCollab: FluidClientVersion.v2_0,
};
const defaultFieldBatchVersion = 1;
const changeFamily =
Expand Down
9 changes: 5 additions & 4 deletions packages/dds/tree/src/simple-tree/api/storedSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* Licensed under the MIT License.
*/

import type { FluidClientVersion, ICodecOptions } from "../../codec/index.js";
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
import type { ICodecOptions } from "../../codec/index.js";
import { SchemaVersion } from "../../core/index.js";
import { encodeTreeSchema, makeSchemaCodec } from "../../feature-libraries/index.js";
import {
Expand All @@ -24,7 +25,7 @@ import type { SchemaCompatibilityStatus } from "./tree.js";
* Dumps the "persisted" schema subset of the provided `schema` into a deterministic JSON-compatible, semi-human-readable format.
*
* @param schema - The schema to dump.
* @param oldestCompatibleClient - The oldest client version which can read the schema: impacts the format used.
* @param minVersionForCollab - The oldest client version which can read the schema: impacts the format used.
* @param includeStaged - filter for selecting which staged allowed types to include in the output.
*
* @remarks
Expand Down Expand Up @@ -54,11 +55,11 @@ import type { SchemaCompatibilityStatus } from "./tree.js";
*/
export function extractPersistedSchema(
schema: ImplicitFieldSchema,
oldestCompatibleClient: FluidClientVersion,
minVersionForCollab: MinimumVersionForCollab,
includeStaged: (upgrade: SchemaUpgrade) => boolean,
): JsonCompatible {
const stored = toStoredSchema(schema, { includeStaged });
const schemaWriteVersion = clientVersionToSchemaVersion(oldestCompatibleClient);
const schemaWriteVersion = clientVersionToSchemaVersion(minVersionForCollab);
return encodeTreeSchema(stored, schemaWriteVersion);
}

Expand Down
Loading
Loading