Skip to content
1 change: 1 addition & 0 deletions packages/common/client-utils/src/indexBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export {
export { createEmitter } from "./events/index.js";

export {
type FluidLayer,
checkLayerCompatibility,
type LayerCompatCheckResult,
type ILayerCompatDetails,
Expand Down
1 change: 1 addition & 0 deletions packages/common/client-utils/src/indexNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export {
export { createEmitter } from "./events/index.js";

export {
type FluidLayer,
checkLayerCompatibility,
type LayerCompatCheckResult,
type ILayerCompatDetails,
Expand Down
6 changes: 6 additions & 0 deletions packages/common/client-utils/src/layerCompat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
* Licensed under the MIT License.
*/

/**
* The different Fluid layers in a client.
* @internal
*/
export type FluidLayer = "loader" | "driver" | "runtime" | "dataStore";

/**
* Result of a layer compatibility check - whether a layer is compatible with another layer.
* @internal
Expand Down
55 changes: 55 additions & 0 deletions packages/common/core-interfaces/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,58 @@ export interface IThrottlingWarning extends IErrorBase {
readonly errorType: typeof FluidErrorTypes.throttlingError;
readonly retryAfterSeconds: number;
}

/**
* Symbol used to identify an error of type {@link ILayerIncompatibilityError}.
* @legacy @alpha
*/
export const layerIncompatibilityErrorSymbol: unique symbol = Symbol(
"LayerIncompatibilityError",
);

/**
* Usage error indicating that two Fluid layers are incompatible. For instance, if the Loader layer is
* not compatible with the Runtime layer, the container will be disposed with this error.
* @legacy @alpha
*/
export interface ILayerIncompatibilityError extends IErrorBase {
/**
* {@inheritDoc IErrorBase.errorType}
*/
readonly errorType: typeof FluidErrorTypes.usageError;

/**
* Symbol used to identify this error as a layer incompatibility error.
*/
readonly [layerIncompatibilityErrorSymbol]: true;
/**
* The layer that is reporting the incompatibility.
*/
readonly layer: string;
/**
* The layer that is incompatible with the reporting layer.
*/
readonly incompatibleLayer: string;
/**
* The package version of the reporting layer.
*/
readonly layerVersion: string;
/**
* The package version of the incompatible layer.
*/
readonly incompatibleLayerVersion: string;
/**
* The number of months of compatibility requirements between the two layers as per the layer compatibility policy.
*/
readonly compatibilityRequirementsInMonths: number;
/**
* The minimum actual difference in months between the release of the two layers.
* Note that for layers with package versions older than 2.63.0, the actual difference may be higher than this value
* because the difference reported is capped as per 2.63.0 where the compatibility enforcement was introduced.
*/
readonly actualDifferenceInMonths: number;
/**
* Additional details about the incompatibility to be used for debugging purposes.
*/
readonly details: string;
}
9 changes: 8 additions & 1 deletion packages/common/core-interfaces/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ export type { BrandedType } from "./brandedType.js";

export type { IDisposable } from "./disposable.js";

export type { IErrorBase, IGenericError, IUsageError, IThrottlingWarning } from "./error.js";
export type {
IErrorBase,
IGenericError,
IUsageError,
IThrottlingWarning,
ILayerIncompatibilityError,
} from "./error.js";
export { layerIncompatibilityErrorSymbol } from "./error.js";
export { FluidErrorTypes } from "./error.js";

export type {
Expand Down
6 changes: 5 additions & 1 deletion packages/loader/container-loader/src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@ export class Container
validateDriverCompatibility(
maybeDriverCompatDetails.ILayerCompatDetails,
(error) => {} /* disposeFn */, // There is nothing to dispose here, so just ignore the error.
subLogger,
);

this.connectionTransitionTimes[ConnectionState.Disconnected] = performanceNow();
Expand Down Expand Up @@ -2481,7 +2482,10 @@ export class Container

// Validate that the Runtime is compatible with this Loader.
const maybeRuntimeCompatDetails = runtime as FluidObject<ILayerCompatDetails>;
validateRuntimeCompatibility(maybeRuntimeCompatDetails.ILayerCompatDetails);
validateRuntimeCompatibility(
maybeRuntimeCompatDetails.ILayerCompatDetails,
this.mc.logger,
);

this._runtime = runtime;
this._lifecycleEvents.emit("runtimeInstantiated");
Expand Down
57 changes: 21 additions & 36 deletions packages/loader/container-loader/src/loaderLayerCompatState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
* Licensed under the MIT License.
*/

import {
checkLayerCompatibility,
type ILayerCompatDetails,
type ILayerCompatSupportRequirements,
import type {
ILayerCompatDetails,
ILayerCompatSupportRequirements,
} from "@fluid-internal/client-utils";
import type { ICriticalContainerError } from "@fluidframework/container-definitions";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import {
validateLayerCompatibility,
type ITelemetryLoggerExt,
} from "@fluidframework/telemetry-utils/internal";

import { pkgVersion } from "./packageVersion.js";

Expand Down Expand Up @@ -78,25 +80,17 @@ export const driverSupportRequirementsForLoader: ILayerCompatSupportRequirements
*/
export function validateRuntimeCompatibility(
maybeRuntimeCompatDetails: ILayerCompatDetails | undefined,
logger: ITelemetryLoggerExt,
): void {
const layerCheckResult = checkLayerCompatibility(
validateLayerCompatibility(
"loader",
"runtime",
loaderCompatDetailsForRuntime,
runtimeSupportRequirementsForLoader,
maybeRuntimeCompatDetails,
() => {} /* disposeFn - no op. This will be handled by the caller */,
logger,
);
if (!layerCheckResult.isCompatible) {
const error = new UsageError("Loader is not compatible with Runtime", {
errorDetails: JSON.stringify({
loaderVersion: loaderCompatDetailsForRuntime.pkgVersion,
runtimeVersion: maybeRuntimeCompatDetails?.pkgVersion,
loaderGeneration: loaderCompatDetailsForRuntime.generation,
runtimeGeneration: maybeRuntimeCompatDetails?.generation,
minSupportedGeneration: runtimeSupportRequirementsForLoader.minSupportedGeneration,
isGenerationCompatible: layerCheckResult.isGenerationCompatible,
unsupportedFeatures: layerCheckResult.unsupportedFeatures,
}),
});
throw error;
}
}

/**
Expand All @@ -106,24 +100,15 @@ export function validateRuntimeCompatibility(
export function validateDriverCompatibility(
maybeDriverCompatDetails: ILayerCompatDetails | undefined,
disposeFn: (error?: ICriticalContainerError) => void,
logger: ITelemetryLoggerExt,
): void {
const layerCheckResult = checkLayerCompatibility(
validateLayerCompatibility(
"loader",
"driver",
loaderCompatDetailsForRuntime,
driverSupportRequirementsForLoader,
maybeDriverCompatDetails,
disposeFn,
logger,
);
if (!layerCheckResult.isCompatible) {
const error = new UsageError("Loader is not compatible with Driver", {
errorDetails: JSON.stringify({
loaderVersion: loaderCoreCompatDetails.pkgVersion,
driverVersion: maybeDriverCompatDetails?.pkgVersion,
loaderGeneration: loaderCoreCompatDetails.generation,
driverGeneration: maybeDriverCompatDetails?.generation,
minSupportedGeneration: driverSupportRequirementsForLoader.minSupportedGeneration,
isGenerationCompatible: layerCheckResult.isGenerationCompatible,
unsupportedFeatures: layerCheckResult.unsupportedFeatures,
}),
});
disposeFn(error);
throw error;
}
}
Loading
Loading