Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions .changeset/warm-windows-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@fluidframework/tree": 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`).
The `oldestCompatibleClient` is still specified at the Shared Tree API level rather than by the Container,
though this will change someday.
13 changes: 6 additions & 7 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,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 @@ -169,7 +169,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 @@ -257,11 +257,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";
};

// @beta @input
export interface ForestOptions {
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 @@ -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/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
16 changes: 7 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,17 @@ describe("versioned Codecs", () => {
]);
const builder = new ClientVersionDispatchingCodecBuilder(
family,
(oldestCompatibleClient: FluidClientVersion) => (oldestCompatibleClient > 5 ? 2 : 1),
(oldestCompatibleClient: MinimumVersionForCollab) =>
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,
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
23 changes: 19 additions & 4 deletions packages/dds/tree/src/treeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { SharedTreeFactoryType, SharedTreeAttributes } from "./sharedTreeAttribu
import type { ITree } from "./simple-tree/index.js";
import { Breakable } from "./util/index.js";
import { FluidClientVersion } from "./codec/index.js";
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";

/**
* {@link ITreePrivate} extended with ISharedObject.
Expand All @@ -35,6 +36,14 @@ import { FluidClientVersion } from "./codec/index.js";
*/
export interface ISharedTree extends ISharedObject, ITreePrivate {}

function selectFeatureFlags(
optionsOverride: SharedTreeOptionsInternal,
minVersionForCollab: MinimumVersionForCollab,
): SharedTreeOptionsInternal {
// Passthrough for now. Someday this will alter feature flags and return a copy with the changes.
return optionsOverride;
}

/**
* Creates a factory for shared tree kernels with the given options.
* @remarks
Expand All @@ -48,10 +57,16 @@ 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 adjustedOptions = {
...selectFeatureFlags(
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.
options.oldestCompatibleClient ?? 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.

Loading