The utilities module provides helper functions for container management, URL handling, location redirection, and compatibility checks.
Waits for a container to finish synchronizing with the latest state from the service.
function waitContainerToCatchUp(container: IContainer): Promise<boolean>;This function is particularly useful after reconnection events to ensure the container has processed all pending operations.
import { waitContainerToCatchUp } from "@fluidframework/container-loader";
async function handleReconnection(container: IContainer) {
console.log("Container reconnected, waiting to catch up...");
const success = await waitContainerToCatchUp(container);
if (success) {
console.log("Container successfully caught up with latest state");
// Safe to continue with operations
await performContainerOperations(container);
} else {
console.error("Container failed to catch up within timeout");
// Handle failure scenario
await handleCatchUpFailure(container);
}
}
// Use with connection event handlers
container.on("connected", () => handleReconnection(container));Attempts to parse a URL into a compatible resolved URL structure.
function tryParseCompatibleResolvedUrl(url: string): IParsedUrl | undefined;Returns a parsed URL object if the URL can be parsed, or undefined if parsing fails.
import { tryParseCompatibleResolvedUrl } from "@fluidframework/container-loader";
import type { IParsedUrl } from "@fluidframework/container-loader";
function handleUrlParsing(urlString: string) {
const parsedUrl: IParsedUrl | undefined = tryParseCompatibleResolvedUrl(urlString);
if (parsedUrl) {
console.log("Successfully parsed URL:");
console.log(" ID:", parsedUrl.id);
console.log(" Path:", parsedUrl.path);
console.log(" Query:", parsedUrl.query);
console.log(" Version:", parsedUrl.version);
// Use parsed components
if (parsedUrl.version) {
console.log(`Loading specific version: ${parsedUrl.version}`);
}
} else {
console.log("Unable to parse URL:", urlString);
// Handle parsing failure
}
}Resolves requests with automatic handling of location redirection errors.
function resolveWithLocationRedirectionHandling<T>(
api: (request: IRequest) => Promise<T>,
request: IRequest,
urlResolver: IUrlResolver,
logger?: ITelemetryBaseLogger
): Promise<T>;This utility wraps API calls to automatically handle location redirection scenarios common in distributed Fluid deployments.
import {
resolveWithLocationRedirectionHandling
} from "@fluidframework/container-loader";
import type {
IRequest,
IUrlResolver,
ITelemetryBaseLogger
} from "@fluidframework/container-loader";
async function loadContainerWithRedirection(
request: IRequest,
urlResolver: IUrlResolver,
logger?: ITelemetryBaseLogger
) {
const containerLoadApi = async (req: IRequest) => {
// Your container loading logic
const resolvedUrl = await urlResolver.resolve(req);
return await loadContainerFromUrl(resolvedUrl);
};
try {
const container = await resolveWithLocationRedirectionHandling(
containerLoadApi,
request,
urlResolver,
logger
);
console.log("Container loaded successfully:", container.id);
return container;
} catch (error) {
console.error("Failed to load container after redirection handling:", error);
throw error;
}
}function isLocationRedirectionError(error: any): boolean;Determines if an error is a location redirection error that can be handled automatically.
import { isLocationRedirectionError } from "@fluidframework/container-loader";
async function handleContainerLoadError(error: any, request: IRequest) {
if (isLocationRedirectionError(error)) {
console.log("Detected location redirection error");
// Extract new location from error
const redirectUrl = error.redirectUrl || error.location;
if (redirectUrl) {
console.log(`Redirecting to: ${redirectUrl}`);
// Create new request with redirected URL
const redirectedRequest: IRequest = {
...request,
url: redirectUrl
};
// Retry with redirected URL
return await loadContainer(redirectedRequest);
}
}
// Not a redirection error, re-throw
throw error;
}Loads a container in a paused state, optionally up to a specific sequence number.
function loadContainerPaused(
loaderProps: ILoaderProps,
request: IRequest,
loadToSequenceNumber?: number,
signal?: AbortSignal,
): Promise<IContainer>;This function is useful for loading containers for debugging, analysis, or when you need to examine state at a specific point in time.
import { loadContainerPaused } from "@fluidframework/container-loader";
import type { ILoaderProps, IRequest } from "@fluidframework/container-loader";
async function loadContainerForDebugging(
loaderProps: ILoaderProps,
request: IRequest,
targetSequenceNumber?: number
) {
// Create abort controller for timeout
const abortController = new AbortController();
const timeoutId = setTimeout(() => {
abortController.abort();
}, 30000); // 30 second timeout
try {
const pausedContainer = await loadContainerPaused(
loaderProps,
request,
targetSequenceNumber,
abortController.signal
);
clearTimeout(timeoutId);
console.log("Container loaded in paused state");
console.log("Current sequence number:", pausedContainer.deltaManager.lastSequenceNumber);
if (targetSequenceNumber) {
console.log(`Loaded up to sequence number: ${targetSequenceNumber}`);
}
// Examine container state while paused
const snapshot = pausedContainer.serialize();
console.log("Container snapshot length:", snapshot.length);
// Resume when ready
pausedContainer.resume();
return pausedContainer;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
console.error("Container loading timed out");
} else {
console.error("Error loading paused container:", error);
}
throw error;
}
}interface IParsedUrl {
id: string;
path: string;
query: string;
version: string | undefined;
}Represents the structure of a parsed Fluid URL with its constituent components.
The loader provides several compatibility constants for version checking and feature detection.
const loaderCoreCompatDetails: ILayerCompatDetails;
const loaderCompatDetailsForRuntime: ILayerCompatDetails;
const runtimeSupportRequirementsForLoader: ILayerCompatSupportRequirements;
const driverSupportRequirementsForLoader: ILayerCompatSupportRequirements;import {
loaderCoreCompatDetails,
loaderCompatDetailsForRuntime,
runtimeSupportRequirementsForLoader,
driverSupportRequirementsForLoader
} from "@fluidframework/container-loader";
function checkCompatibility() {
console.log("Loader core compatibility:", loaderCoreCompatDetails);
console.log("Runtime compatibility requirements:", runtimeSupportRequirementsForLoader);
console.log("Driver compatibility requirements:", driverSupportRequirementsForLoader);
// Use for version checking and feature detection
const isCompatible = checkVersionCompatibility(
runtimeVersion,
runtimeSupportRequirementsForLoader
);
if (!isCompatible) {
throw new Error("Runtime version incompatible with loader requirements");
}
}import {
resolveWithLocationRedirectionHandling,
isLocationRedirectionError,
waitContainerToCatchUp
} from "@fluidframework/container-loader";
async function robustContainerLoad(
request: IRequest,
loaderProps: ILoaderProps,
maxRetries: number = 3
): Promise<IContainer> {
const loader = new Loader(loaderProps);
let lastError: Error | undefined;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
// Use redirection handling for robust loading
const container = await resolveWithLocationRedirectionHandling(
(req) => loader.resolve(req),
request,
loaderProps.urlResolver,
loaderProps.logger
);
// Wait for container to catch up
const success = await waitContainerToCatchUp(container);
if (!success) {
throw new Error("Container failed to catch up");
}
console.log(`Container loaded successfully on attempt ${attempt}`);
return container;
} catch (error) {
lastError = error as Error;
console.warn(`Container load attempt ${attempt} failed:`, error);
if (isLocationRedirectionError(error)) {
console.log("Redirection error will be retried");
} else if (attempt === maxRetries) {
console.error("Max retries exceeded");
break;
}
// Exponential backoff
const delay = Math.pow(2, attempt - 1) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError || new Error("Container load failed after retries");
}import { tryParseCompatibleResolvedUrl } from "@fluidframework/container-loader";
function validateAndNormalizeUrl(urlString: string): string {
// First, try to parse the URL
const parsedUrl = tryParseCompatibleResolvedUrl(urlString);
if (!parsedUrl) {
throw new Error(`Invalid Fluid URL format: ${urlString}`);
}
// Validate required components
if (!parsedUrl.id) {
throw new Error("URL must contain a valid document ID");
}
// Normalize the URL components
const normalizedUrl = constructFluidUrl({
id: parsedUrl.id.trim(),
path: parsedUrl.path.startsWith('/') ? parsedUrl.path : `/${parsedUrl.path}`,
query: parsedUrl.query.startsWith('?') ? parsedUrl.query : `?${parsedUrl.query}`,
version: parsedUrl.version?.trim()
});
console.log(`Normalized URL: ${normalizedUrl}`);
return normalizedUrl;
}async function handleContainerErrors(
container: IContainer,
onError: (error: Error) => void
) {
container.on("closed", (error) => {
if (error) {
console.error("Container closed with error:", error);
if (isLocationRedirectionError(error)) {
console.log("Attempting recovery with redirection");
// Trigger redirection recovery
} else {
onError(error);
}
}
});
container.on("disconnected", async () => {
console.log("Container disconnected, waiting for reconnection...");
// Set up timeout for reconnection
const reconnectTimeout = setTimeout(() => {
console.error("Reconnection timeout exceeded");
onError(new Error("Failed to reconnect within timeout"));
}, 30000);
container.once("connected", async () => {
clearTimeout(reconnectTimeout);
console.log("Container reconnected");
try {
const success = await waitContainerToCatchUp(container);
if (!success) {
throw new Error("Failed to catch up after reconnection");
}
console.log("Container fully synchronized after reconnection");
} catch (error) {
onError(error as Error);
}
});
});
}