Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
23 changes: 23 additions & 0 deletions .changeset/warm-windows-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@fluidframework/tree": minor
"fluid-framework": minor
"__section": tree
---
MinimumVersionForCollab is now used in place of FluidClientVersion

FluidClientVersion is no longer used as the declaration type for versions in APIs/codecs (for example, `oldestCompatibleClient`).
Additionally, FluidClientVersion is now a const object with members that declare specific MinimumVersionForCollab versions.
These are intended to be used with APIs that require a version (such as `TreeAlpha.exportCompressed`).

`SharedTreeOptions.oldestCompatibleClient` has been removed in favor of `LoadContainerRuntimeParams.minVersionForCollab`.
If an application previously specified the minimum client version when initialization Shared Tree like:

```ts
const factory = configuredSharedTree({ ..., oldestCompatibleClient: "2.1.3" });
```

The application should now specify the version when initializing the Container Runtime:

```ts
const runtime = await loadContainerRuntime({ ..., minVersionForCollab: "2.1.3" });
```
31 changes: 15 additions & 16 deletions packages/dds/tree/api-report/tree.alpha.api.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/dds/tree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,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
39 changes: 11 additions & 28 deletions packages/dds/tree/src/codec/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import type { Static, TAnySchema, TSchema } from "@sinclair/typebox";
import type { ChangeEncodingContext } from "../core/index.js";
import type { JsonCompatibleReadOnly } from "../util/index.js";
import { noopValidator } from "./noopValidator.js";
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
import { defaultMinVersionForCollab } from "@fluidframework/runtime-utils/internal";

/**
* Translates decoded data to encoded data.
Expand Down Expand Up @@ -145,7 +147,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 oldestCompatibleClient: MinimumVersionForCollab;
}

/**
Expand Down Expand Up @@ -414,16 +416,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 @@ -435,7 +427,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 @@ -445,8 +437,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: defaultMinVersionForCollab,

/** 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 @@ -457,19 +451,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 @@ -480,4 +463,4 @@ 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;
8 changes: 5 additions & 3 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 Down Expand Up @@ -115,11 +115,13 @@ 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: (
oldestCompatibleClient: MinimumVersionForCollab,
) => number,
) {}

public build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import type { IIdCompressor, SessionId } from "@fluidframework/id-compressor";

import {
type FluidClientVersion,
type ICodecOptions,
type IJsonCodec,
makeVersionedValidatedCodec,
Expand Down Expand Up @@ -36,6 +35,7 @@ import type { FieldBatch } from "./fieldBatch.js";
import { EncodedFieldBatch, validVersions } from "./format.js";
import { schemaCompressedEncode } from "./schemaBasedEncode.js";
import { uncompressedEncode } from "./uncompressedEncode.js";
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";

/**
* Reference ID for a chunk that is incrementally encoded.
Expand Down Expand Up @@ -124,7 +124,7 @@ export type FieldBatchCodec = IJsonCodec<
* 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,
oldestCompatibleClient: 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 @@ -6,7 +6,6 @@
import { fail, unreachableCase } from "@fluidframework/core-utils/internal";

import {
type FluidClientVersion,
type ICodecFamily,
type ICodecOptions,
type IJsonCodec,
Expand All @@ -28,14 +27,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
5 changes: 3 additions & 2 deletions packages/dds/tree/src/shared-tree/sharedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,13 +581,14 @@ export type SharedTreeFormatVersion = typeof SharedTreeFormatVersion;
* Configuration options for SharedTree.
* @alpha @input
*/
export type SharedTreeOptions = Partial<CodecWriteOptions> &
export type SharedTreeOptions = Partial<ICodecOptions> &
Partial<SharedTreeFormatOptions> &
ForestOptions;

export interface SharedTreeOptionsInternal
extends Omit<SharedTreeOptions, "treeEncodeType">,
Partial<SharedTreeFormatOptionsInternal> {
Partial<SharedTreeFormatOptionsInternal>,
Partial<CodecWriteOptions> {
disposeForksAfterTransaction?: boolean;
/**
* Returns whether a field should be incrementally encoded.
Expand Down
5 changes: 3 additions & 2 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 Down Expand Up @@ -58,7 +59,7 @@ import type { SchemaCompatibilityStatus } from "./tree.js";
*/
export function extractPersistedSchema(
schema: ImplicitAnnotatedFieldSchema,
oldestCompatibleClient: FluidClientVersion,
oldestCompatibleClient: MinimumVersionForCollab,
includeStaged: (upgrade: SchemaUpgrade) => boolean,
): JsonCompatible {
const stored = toStoredSchema(schema, { includeStaged });
Expand Down
18 changes: 9 additions & 9 deletions packages/dds/tree/src/test/codec/versioned/codec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@

import { strict as assert } from "node:assert";

import {
type FluidClientVersion,
type ICodecFamily,
type IJsonCodec,
makeCodecFamily,
} from "../../../codec/index.js";
import { type ICodecFamily, type IJsonCodec, makeCodecFamily } from "../../../codec/index.js";
import { typeboxValidator } from "../../../external-utilities/index.js";
// eslint-disable-next-line import/no-internal-modules
import { ClientVersionDispatchingCodecBuilder } from "../../../codec/versioned/codec.js";
import { validateUsageError } from "../../utils.js";
import { pkgVersion } from "../../../packageVersion.js";
import { gt } from "semver-ts";
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";

describe("versioned Codecs", () => {
describe("ClientVersionDispatchingCodecBuilder", () => {
Expand All @@ -42,16 +39,19 @@ describe("versioned Codecs", () => {
]);
const builder = new ClientVersionDispatchingCodecBuilder(
family,
(oldestCompatibleClient: FluidClientVersion) => (oldestCompatibleClient > 5 ? 2 : 1),
(oldestCompatibleClient: MinimumVersionForCollab) =>
// Arbitrary version selection logic for test purposes. Versions greater than 5.0.0 get v2 codec.
gt(oldestCompatibleClient, "5.0.0") ? 2 : 1,
);

it("round trip", () => {
const codec1 = builder.build({
oldestCompatibleClient: 2 as FluidClientVersion,
oldestCompatibleClient: "2.0.0",
jsonValidator: typeboxValidator,
});
const codec2 = builder.build({
oldestCompatibleClient: 6 as FluidClientVersion,
// We have to cast to a `MinimumVersionForCollab` because "6.0.0" is not a valid value for that type.
oldestCompatibleClient: "6.0.0" as MinimumVersionForCollab,
jsonValidator: typeboxValidator,
});
const v1 = codec1.encode(42);
Expand Down
6 changes: 3 additions & 3 deletions packages/dds/tree/src/test/shared-tree/fuzz/baseModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { SharedTreeFuzzTestFactory, createOnCreate } from "./fuzzUtils.js";
import type { Operation } from "./operationTypes.js";
import { takeAsync } from "@fluid-private/stochastic-test-utils";
import { type EditGeneratorOpWeights, makeOpGenerator } from "./fuzzEditGenerators.js";
import { FluidClientVersion } from "../../../codec/index.js";
import { ForestTypeOptimized, ForestTypeReference } from "../../../shared-tree/index.js";
import { pkgVersion } from "../../../packageVersion.js";

export const runsPerBatch = 50;
// TODO: Enable other types of ops.
Expand Down Expand Up @@ -41,7 +41,7 @@ export const baseTreeModel: DDSFuzzModel<
> = {
workloadName: "SharedTree (Reference Forest)",
factory: new SharedTreeFuzzTestFactory(createOnCreate(undefined), undefined, {
oldestCompatibleClient: FluidClientVersion.EnableUnstableFeatures,
oldestCompatibleClient: pkgVersion,
forest: ForestTypeReference,
}),
generatorFactory,
Expand All @@ -56,7 +56,7 @@ export const optimizedForestTreeModel: DDSFuzzModel<
> = {
workloadName: "SharedTree (Optimized Forest)",
factory: new SharedTreeFuzzTestFactory(createOnCreate(undefined), undefined, {
oldestCompatibleClient: FluidClientVersion.EnableUnstableFeatures,
oldestCompatibleClient: pkgVersion,
forest: ForestTypeOptimized,
}),
generatorFactory,
Expand Down
6 changes: 4 additions & 2 deletions packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2410,11 +2410,13 @@ describe("SharedTree", () => {
});

it("summarize with pre-attach removed nodes", () => {
const runtime = new MockFluidDataStoreRuntime({ idCompressor: createIdCompressor() });
const runtime = new MockFluidDataStoreRuntime({
idCompressor: createIdCompressor(),
minVersionForCollab: FluidClientVersion.v2_52,
});
const sharedObject = configuredSharedTree({
jsonValidator: typeboxValidator,
forest: ForestTypeExpensiveDebug,
oldestCompatibleClient: FluidClientVersion.v2_52,
}) as SharedObjectKind<ISharedTree> & ISharedObjectKind<ISharedTree>;
const tree = sharedObject.getFactory().create(runtime, "tree");
const runtimeFactory = new MockContainerRuntimeFactory();
Expand Down
16 changes: 12 additions & 4 deletions packages/dds/tree/src/treeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,18 @@ function treeKernelFactory(
if (args.idCompressor === undefined) {
throw new UsageError("IdCompressor must be enabled to use SharedTree");
}
const adjustedOptions = { ...options };
// TODO: get default from runtime once something like runtime.oldestCompatibleClient exists.
// Using default of 2.0 since that is the oldest version that supports SharedTree.
adjustedOptions.oldestCompatibleClient ??= FluidClientVersion.v2_0;

const { oldestCompatibleClient, ...otherOptions } = options;

const adjustedOptions = {
...otherOptions,
// We allow setting oldestCompatibleClient via `SharedTreeOptionsInternal` for testing purposes. This type is non-public.
// TODO: get default from runtime once something like runtime.oldestCompatibleClient exists.
// Using default of 2.0 since that is the oldest version that supports SharedTree.
oldestCompatibleClient:
oldestCompatibleClient ?? args.minVersionForCollab ?? FluidClientVersion.v2_0,
};

return new SharedTreeKernel(
new Breakable("SharedTree"),
args.sharedObject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export function cloneWithReplacements(root: unknown, rootKey: string, replacer:

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

// @public
Expand Down Expand Up @@ -217,7 +217,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: ImplicitAnnotatedFieldSchema, oldestCompatibleClient: FluidClientVersion, includeStaged: (upgrade: SchemaUpgrade) => boolean): JsonCompatible;
export function extractPersistedSchema(schema: ImplicitAnnotatedFieldSchema, oldestCompatibleClient: 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 @@ -305,11 +305,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-defaults";
readonly v2_52: "2.52.0";
};

// @public
export type FluidObject<T = unknown> = {
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.