diff --git a/packages/common/container-definitions/api-report/container-definitions.legacy.beta.api.md b/packages/common/container-definitions/api-report/container-definitions.legacy.beta.api.md index 9cd30d8bcdb9..8b9a872fd978 100644 --- a/packages/common/container-definitions/api-report/container-definitions.legacy.beta.api.md +++ b/packages/common/container-definitions/api-report/container-definitions.legacy.beta.api.md @@ -147,7 +147,7 @@ export interface IContainer extends IEventProvider { attach(request: IRequest, attachProps?: { deltaConnection?: "none" | "delayed"; }): Promise; - readonly attachState: AttachState; + readonly attachState: AttachState | undefined; readonly audience: IAudience; readonly clientId?: string | undefined; close(error?: ICriticalContainerError): void; diff --git a/packages/common/container-definitions/package.json b/packages/common/container-definitions/package.json index e3c838939e78..7dfa47ba23da 100644 --- a/packages/common/container-definitions/package.json +++ b/packages/common/container-definitions/package.json @@ -110,7 +110,11 @@ "typescript": "~5.4.5" }, "typeValidation": { - "broken": {}, + "broken": { + "Interface_IContainer": { + "backCompat": false + } + }, "entrypoint": "legacy" } } diff --git a/packages/common/container-definitions/src/loader.ts b/packages/common/container-definitions/src/loader.ts index 703c80a9dad6..a16424d68afc 100644 --- a/packages/common/container-definitions/src/loader.ts +++ b/packages/common/container-definitions/src/loader.ts @@ -352,8 +352,9 @@ export interface IContainer extends IEventProvider { /** * Indicates the attachment state of the container to a host service. + * Will be `undefined` when the container is being loaded and the state is not yet known. */ - readonly attachState: AttachState; + readonly attachState: AttachState | undefined; /** * Get the code details that are currently specified for the container. diff --git a/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts b/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts index 7f01d94ec8b5..538a3c0e058a 100644 --- a/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts +++ b/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts @@ -240,6 +240,7 @@ declare type old_as_current_for_Interface_IContainer = requireAssignableTo, TypeOnly> /* diff --git a/packages/framework/fluid-static/src/fluidContainer.ts b/packages/framework/fluid-static/src/fluidContainer.ts index 0f57df727a36..a981f5dc4db4 100644 --- a/packages/framework/fluid-static/src/fluidContainer.ts +++ b/packages/framework/fluid-static/src/fluidContainer.ts @@ -341,7 +341,10 @@ class FluidContainer } public get attachState(): AttachState { - return this.container.attachState; + // attachState can be undefined on IContainer while loading, but FluidContainer is only + // created after loading completes, so attachState is always defined here. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.container.attachState!; } public get disposed(): boolean { diff --git a/packages/loader/container-loader/src/container.ts b/packages/loader/container-loader/src/container.ts index f6035607f005..a6ae5886a03d 100644 --- a/packages/loader/container-loader/src/container.ts +++ b/packages/loader/container-loader/src/container.ts @@ -558,7 +558,7 @@ export class Container private readonly connectionTransitionTimes: number[] = []; private _loadedFromVersion: IVersion | undefined; private _dirtyContainer = false; - private attachmentData: AttachmentData = { state: AttachState.Detached }; + private attachmentData: AttachmentData | undefined; private readonly serializedStateManager: SerializedStateManager; private readonly _containerId: string; @@ -757,6 +757,12 @@ export class Container this.connectionTransitionTimes[ConnectionState.Disconnected] = performanceNow(); const pendingLocalState = loadProps?.pendingLocalState; + // When loading an existing container, the attachment state is unknown until the load + // completes. Only initialize to Detached when creating a new container. + if (loadProps === undefined) { + this.attachmentData = { state: AttachState.Detached }; + } + this._canReconnect = canReconnect ?? true; this.clientDetailsOverride = clientDetailsOverride; this.urlResolver = urlResolver; @@ -1155,7 +1161,7 @@ export class Container ); } assert( - this.attachmentData.state === AttachState.Attached, + this.attachmentData?.state === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */, ); assert( @@ -1170,8 +1176,8 @@ export class Container return pendingState; } - public get attachState(): AttachState { - return this.attachmentData.state; + public get attachState(): AttachState | undefined { + return this.attachmentData?.state; } /** @@ -1181,7 +1187,11 @@ export class Container * @returns stringified {@link IPendingDetachedContainerState} for the container */ public serialize(): string { - if (this.attachmentData.state === AttachState.Attached || this.closed) { + if ( + this.attachmentData === undefined || + this.attachmentData.state === AttachState.Attached || + this.closed + ) { throw new UsageError("Container must not be attached or closed."); } @@ -1222,6 +1232,7 @@ export class Container async () => { if ( this._lifecycleState !== "loaded" || + this.attachmentData === undefined || this.attachmentData.state === AttachState.Attached ) { // pre-0.58 error message: containerNotValidForAttach @@ -1243,7 +1254,7 @@ export class Container const setAttachmentData: AttachProcessProps["setAttachmentData"] = ( attachmentData, ) => { - const previousState = this.attachmentData.state; + const previousState = this.attachmentData?.state; this.attachmentData = attachmentData; const state = this.attachmentData.state; if (state !== previousState && state !== AttachState.Detached) { @@ -2422,7 +2433,7 @@ export class Container getAbsoluteUrl: this.getAbsoluteUrl, getContainerDiagnosticId: () => this.resolvedUrl?.id, getClientId: () => this.clientId, - getAttachState: () => this.attachState, + getAttachState: () => this.attachState as AttachState, getConnected: () => this.connected, getConnectionState: () => this.connectionState, clientDetails: this._deltaManager.clientDetails,