Skip to content

Commit 9c14d82

Browse files
ContainerLoader: Move getPendingLocalState to legacy/alpha (microsoft#25513)
This pull request introduces a new alpha interface, `ContainerAlpha`, to expose experimental APIs on `IContainer` in a safer and more forward-compatible way. It replaces the previous `IContainerExperimental` approach across the codebase, introduces an `asLegacyAlpha` type-casting helper, and updates all relevant tests and build configurations to use the new pattern. Additionally, new API extractor configurations and reporting are added to support the alpha interface.
1 parent a0098da commit 9c14d82

23 files changed

+485
-214
lines changed

experimental/dds/tree/src/test/utilities/TestUtilities.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
type IFluidCodeDetails,
1313
type IHostLoader,
1414
} from '@fluidframework/container-definitions/internal';
15-
import { IContainerExperimental, Loader, waitContainerToCatchUp } from '@fluidframework/container-loader/internal';
15+
import { asLegacyAlpha, Loader, waitContainerToCatchUp } from '@fluidframework/container-loader/internal';
1616
import { DefaultSummaryConfiguration, SummaryCollection } from '@fluidframework/container-runtime/internal';
1717
import type { ConfigTypes, IConfigProviderBase, IFluidHandle, IRequestHeader } from '@fluidframework/core-interfaces';
1818
import { ITelemetryBaseLogger } from '@fluidframework/core-interfaces';
@@ -690,13 +690,13 @@ export async function waitForSummary(mainContainer: IContainer): Promise<string>
690690
*/
691691
export async function withContainerOffline<TReturn>(
692692
provider: ITestObjectProvider,
693-
container: IContainerExperimental,
693+
container: IContainer,
694694
action: () => TReturn
695695
): Promise<{ actionReturn: TReturn; pendingLocalState: string }> {
696696
await provider.ensureSynchronized();
697697
await provider.opProcessingController.pauseProcessing(container);
698698
const actionReturn = action();
699-
const pendingLocalState = await container.getPendingLocalState?.();
699+
const pendingLocalState = await asLegacyAlpha(container).getPendingLocalState();
700700
container.close();
701701
assert(pendingLocalState !== undefined, 0x726 /* pendingLocalState should be defined */);
702702
return { actionReturn, pendingLocalState };

packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

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

8-
import type { IContainerExperimental } from "@fluidframework/container-loader/internal";
8+
import { asLegacyAlpha, type ContainerAlpha } from "@fluidframework/container-loader/internal";
99
import { createIdCompressor } from "@fluidframework/id-compressor/internal";
1010
import { SummaryType } from "@fluidframework/driver-definitions";
1111
import {
@@ -1784,13 +1784,13 @@ describe("SharedTree", () => {
17841784
view1.initialize(["a"]);
17851785
await provider.ensureSynchronized();
17861786

1787-
const pausedContainer: IContainerExperimental = provider.containers[0];
1787+
const pausedContainer: ContainerAlpha = asLegacyAlpha(provider.containers[0]);
17881788
const url = (await pausedContainer.getAbsoluteUrl("")) ?? assert.fail("didn't get url");
17891789
const pausedTree = view1;
17901790
await provider.opProcessingController.pauseProcessing(pausedContainer);
17911791
pausedTree.root.insertAt(1, "b");
17921792
pausedTree.root.insertAt(2, "c");
1793-
const pendingOps = await pausedContainer.getPendingLocalState?.();
1793+
const pendingOps = await pausedContainer.getPendingLocalState();
17941794
pausedContainer.close();
17951795
provider.opProcessingController.resumeProcessing();
17961796

@@ -2092,7 +2092,7 @@ describe("SharedTree", () => {
20922092
it("can apply and resubmit stashed schema ops", async () => {
20932093
const provider = await TestTreeProvider.create(2);
20942094

2095-
const pausedContainer: IContainerExperimental = provider.containers[0];
2095+
const pausedContainer: ContainerAlpha = asLegacyAlpha(provider.containers[0]);
20962096
const url = (await pausedContainer.getAbsoluteUrl("")) ?? assert.fail("didn't get url");
20972097
const pausedTree = provider.trees[0];
20982098
await provider.opProcessingController.pauseProcessing(pausedContainer);
@@ -2103,7 +2103,7 @@ describe("SharedTree", () => {
21032103
}),
21042104
);
21052105
pausedView.initialize([]);
2106-
const pendingOps = await pausedContainer.getPendingLocalState?.();
2106+
const pendingOps = await pausedContainer.getPendingLocalState();
21072107
pausedContainer.close();
21082108
provider.opProcessingController.resumeProcessing();
21092109

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3+
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-lint.entrypoint.json",
4+
"mainEntryPointFilePath": "<projectFolder>/dist/legacyAlpha.d.ts"
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3+
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-lint.entrypoint.json",
4+
"mainEntryPointFilePath": "<projectFolder>/lib/legacyAlpha.d.ts"
5+
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
{
22
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3-
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-report.esm.legacy.json"
3+
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-report.esm.legacy.json",
4+
"mainEntryPointFilePath": "<projectFolder>/lib/legacyAlpha.d.ts",
5+
"apiReport": {
6+
"reportVariants": ["public", "beta", "alpha"]
7+
}
48
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
## Alpha API Report File for "@fluidframework/container-loader"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
// @alpha @legacy
8+
export function asLegacyAlpha(base: IContainer): ContainerAlpha;
9+
10+
// @public
11+
export enum ConnectionState {
12+
CatchingUp = 1,
13+
Connected = 2,
14+
Disconnected = 0,
15+
EstablishingConnection = 3
16+
}
17+
18+
// @alpha @sealed @legacy
19+
export interface ContainerAlpha extends IContainer {
20+
getPendingLocalState(): Promise<string>;
21+
}
22+
23+
// @beta @legacy
24+
export function createDetachedContainer(createDetachedContainerProps: ICreateDetachedContainerProps): Promise<IContainer>;
25+
26+
// @beta @legacy (undocumented)
27+
export interface IBaseProtocolHandler {
28+
// (undocumented)
29+
readonly attributes: IDocumentAttributes;
30+
// (undocumented)
31+
close(): void;
32+
// (undocumented)
33+
getProtocolState(): IScribeProtocolState;
34+
// (undocumented)
35+
processMessage(message: ISequencedDocumentMessage, local: boolean): IProcessMessageResult;
36+
// (undocumented)
37+
readonly quorum: IQuorum;
38+
// (undocumented)
39+
setConnectionState(connected: boolean, clientId: string | undefined): any;
40+
// (undocumented)
41+
snapshot(): IQuorumSnapshot;
42+
}
43+
44+
// @beta @deprecated @legacy (undocumented)
45+
export interface ICodeDetailsLoader extends Partial<IProvideFluidCodeDetailsComparer> {
46+
load(source: IFluidCodeDetails): Promise<IFluidModuleWithDetails>;
47+
}
48+
49+
// @beta @legacy
50+
export interface ICreateAndLoadContainerProps {
51+
readonly allowReconnect?: boolean | undefined;
52+
readonly clientDetailsOverride?: IClientDetails | undefined;
53+
readonly codeLoader: ICodeDetailsLoader_2;
54+
readonly configProvider?: IConfigProviderBase | undefined;
55+
readonly documentServiceFactory: IDocumentServiceFactory;
56+
readonly logger?: ITelemetryBaseLogger | undefined;
57+
readonly options?: IContainerPolicies | undefined;
58+
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder | undefined;
59+
readonly scope?: FluidObject | undefined;
60+
readonly urlResolver: IUrlResolver;
61+
}
62+
63+
// @beta @legacy
64+
export interface ICreateDetachedContainerProps extends ICreateAndLoadContainerProps {
65+
readonly codeDetails: IFluidCodeDetails;
66+
}
67+
68+
// @beta @deprecated @legacy (undocumented)
69+
export interface IFluidModuleWithDetails {
70+
details: IFluidCodeDetails;
71+
module: IFluidModule;
72+
}
73+
74+
// @beta @legacy
75+
export interface ILoaderProps {
76+
readonly codeLoader: ICodeDetailsLoader;
77+
readonly configProvider?: IConfigProviderBase;
78+
readonly documentServiceFactory: IDocumentServiceFactory;
79+
readonly logger?: ITelemetryBaseLogger;
80+
readonly options?: ILoaderOptions;
81+
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
82+
readonly scope?: FluidObject;
83+
readonly urlResolver: IUrlResolver;
84+
}
85+
86+
// @beta @legacy
87+
export interface ILoaderServices {
88+
readonly codeLoader: ICodeDetailsLoader;
89+
readonly documentServiceFactory: IDocumentServiceFactory;
90+
readonly options: ILoaderOptions;
91+
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
92+
readonly scope: FluidObject;
93+
readonly subLogger: ITelemetryLoggerExt;
94+
readonly urlResolver: IUrlResolver;
95+
}
96+
97+
// @beta @legacy
98+
export interface ILoadExistingContainerProps extends ICreateAndLoadContainerProps {
99+
readonly pendingLocalState?: string | undefined;
100+
readonly request: IRequest;
101+
}
102+
103+
// @beta @legacy
104+
export interface IParsedUrl {
105+
id: string;
106+
path: string;
107+
query: string;
108+
version: string | undefined;
109+
}
110+
111+
// @beta @legacy (undocumented)
112+
export interface IProtocolHandler extends IBaseProtocolHandler {
113+
// (undocumented)
114+
readonly audience: IAudienceOwner;
115+
// (undocumented)
116+
processSignal(message: ISignalMessage): any;
117+
}
118+
119+
// @beta @legacy
120+
export interface IQuorumSnapshot {
121+
// (undocumented)
122+
members: QuorumClientsSnapshot;
123+
// (undocumented)
124+
proposals: QuorumProposalsSnapshot["proposals"];
125+
// (undocumented)
126+
values: QuorumProposalsSnapshot["values"];
127+
}
128+
129+
// @beta @legacy
130+
export interface IRehydrateDetachedContainerProps extends ICreateAndLoadContainerProps {
131+
readonly serializedState: string;
132+
}
133+
134+
// @beta @legacy (undocumented)
135+
export interface IScribeProtocolState {
136+
// (undocumented)
137+
members: [string, ISequencedClient][];
138+
// (undocumented)
139+
minimumSequenceNumber: number;
140+
// (undocumented)
141+
proposals: [number, ISequencedProposal, string[]][];
142+
// (undocumented)
143+
sequenceNumber: number;
144+
// (undocumented)
145+
values: [string, ICommittedProposal][];
146+
}
147+
148+
// @beta @legacy
149+
export class Loader implements IHostLoader {
150+
constructor(loaderProps: ILoaderProps);
151+
// (undocumented)
152+
createDetachedContainer(codeDetails: IFluidCodeDetails, createDetachedProps?: {
153+
canReconnect?: boolean;
154+
clientDetailsOverride?: IClientDetails;
155+
}): Promise<IContainer>;
156+
// (undocumented)
157+
rehydrateDetachedContainerFromSnapshot(snapshot: string, createDetachedProps?: {
158+
canReconnect?: boolean;
159+
clientDetailsOverride?: IClientDetails;
160+
}): Promise<IContainer>;
161+
// (undocumented)
162+
resolve(request: IRequest, pendingLocalState?: string): Promise<IContainer>;
163+
// (undocumented)
164+
readonly services: ILoaderServices;
165+
}
166+
167+
// @beta @legacy
168+
export function loadExistingContainer(loadExistingContainerProps: ILoadExistingContainerProps): Promise<IContainer>;
169+
170+
// @beta @legacy
171+
export type ProtocolHandlerBuilder = (attributes: IDocumentAttributes, snapshot: IQuorumSnapshot, sendProposal: (key: string, value: any) => number) => IProtocolHandler;
172+
173+
// @beta @legacy
174+
export type QuorumClientsSnapshot = [string, ISequencedClient][];
175+
176+
// @beta @legacy
177+
export type QuorumProposalsSnapshot = {
178+
proposals: [number, ISequencedProposal, string[]][];
179+
values: [string, ICommittedProposal][];
180+
};
181+
182+
// @beta @legacy
183+
export function rehydrateDetachedContainer(rehydrateDetachedContainerProps: IRehydrateDetachedContainerProps): Promise<IContainer>;
184+
185+
// @beta @legacy
186+
export function resolveWithLocationRedirectionHandling<T>(api: (request: IRequest) => Promise<T>, request: IRequest, urlResolver: IUrlResolver, logger?: ITelemetryBaseLogger): Promise<T>;
187+
188+
// @beta @legacy
189+
export function tryParseCompatibleResolvedUrl(url: string): IParsedUrl | undefined;
190+
191+
// @beta @legacy
192+
export function waitContainerToCatchUp(container: IContainer): Promise<boolean>;
193+
194+
// (No @packageDocumentation comment for this package)
195+
196+
```

packages/loader/container-loader/package.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@
8383
"default": "./dist/index.js"
8484
}
8585
},
86+
"./legacy/alpha": {
87+
"import": {
88+
"types": "./lib/legacyAlpha.d.ts",
89+
"default": "./lib/index.js"
90+
},
91+
"require": {
92+
"types": "./dist/legacyAlpha.d.ts",
93+
"default": "./dist/index.js"
94+
}
95+
},
8696
"./internal": {
8797
"import": {
8898
"types": "./lib/index.d.ts",
@@ -98,8 +108,8 @@
98108
"types": "lib/public.d.ts",
99109
"scripts": {
100110
"api": "fluid-build . --task api",
101-
"api-extractor:commonjs": "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./dist",
102-
"api-extractor:esnext": "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./lib --node10TypeCompat",
111+
"api-extractor:commonjs": "flub generate entrypoints --outFileLegacyBeta legacy --outFileLegacyAlpha legacyAlpha --outDir ./dist",
112+
"api-extractor:esnext": "flub generate entrypoints --outFileLegacyBeta legacy --outFileLegacyAlpha legacyAlpha --outDir ./lib --node10TypeCompat",
103113
"build": "fluid-build . --task build",
104114
"build:api-reports": "concurrently \"npm:build:api-reports:*\"",
105115
"build:api-reports:current": "api-extractor run --local --config api-extractor/api-extractor.current.json",
@@ -117,8 +127,10 @@
117127
"check:exports": "concurrently \"npm:check:exports:*\"",
118128
"check:exports:bundle-release-tags": "api-extractor run --config api-extractor/api-extractor-lint-bundle.json",
119129
"check:exports:cjs:legacy": "api-extractor run --config api-extractor/api-extractor-lint-legacy.cjs.json",
130+
"check:exports:cjs:legacyAlpha": "api-extractor run --config api-extractor/api-extractor-lint-legacyAlpha.cjs.json",
120131
"check:exports:cjs:public": "api-extractor run --config api-extractor/api-extractor-lint-public.cjs.json",
121132
"check:exports:esm:legacy": "api-extractor run --config api-extractor/api-extractor-lint-legacy.esm.json",
133+
"check:exports:esm:legacyAlpha": "api-extractor run --config api-extractor/api-extractor-lint-legacyAlpha.esm.json",
122134
"check:exports:esm:public": "api-extractor run --config api-extractor/api-extractor-lint-public.esm.json",
123135
"check:format": "npm run check:biome",
124136
"ci:build:api-reports": "concurrently \"npm:ci:build:api-reports:*\"",

packages/loader/container-loader/src/container.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ interface IContainerLifecycleEvents extends IEvent {
380380

381381
export class Container
382382
extends EventEmitterWithErrorHandling<IContainerEvents>
383-
implements IContainer, IContainerExperimental
383+
implements IContainer, ContainerAlpha
384384
{
385385
/**
386386
* Load an existing container.
@@ -2558,14 +2558,22 @@ export class Container
25582558

25592559
/**
25602560
* IContainer interface that includes experimental features still under development.
2561-
* @internal
2561+
* @alpha @legacy @sealed
25622562
*/
2563-
export interface IContainerExperimental extends IContainer {
2563+
export interface ContainerAlpha extends IContainer {
25642564
/**
25652565
* Get pending state from container. WARNING: misuse of this API can result in duplicate op
25662566
* submission and potential document corruption. The blob returned MUST be deleted if and when this
25672567
* container emits a "connected" event.
25682568
* @returns serialized blob that can be passed to Loader.resolve()
25692569
*/
2570-
getPendingLocalState?(): Promise<string>;
2570+
getPendingLocalState(): Promise<string>;
2571+
}
2572+
2573+
/**
2574+
* Converts types to their alpha counterparts to expose alpha functionality.
2575+
* @legacy @alpha
2576+
*/
2577+
export function asLegacyAlpha(base: IContainer): ContainerAlpha {
2578+
return base as ContainerAlpha;
25712579
}

packages/loader/container-loader/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
export { ConnectionState } from "./connectionState.js";
7-
export { type IContainerExperimental, waitContainerToCatchUp } from "./container.js";
7+
export { type ContainerAlpha, waitContainerToCatchUp, asLegacyAlpha } from "./container.js";
88
export {
99
createDetachedContainer,
1010
loadExistingContainer,

0 commit comments

Comments
 (0)