@fluidframework/container-loader 2.102.0 → 2.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/api-report/container-loader.legacy.alpha.api.md +21 -8
- package/api-report/container-loader.legacy.beta.api.md +19 -6
- package/dist/connectionManager.d.ts +2 -2
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +3 -3
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +6 -0
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +3 -3
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +2 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/createAndLoadContainerUtils.d.ts +111 -19
- package/dist/createAndLoadContainerUtils.d.ts.map +1 -1
- package/dist/createAndLoadContainerUtils.js +101 -19
- package/dist/createAndLoadContainerUtils.js.map +1 -1
- package/dist/debugLogger.d.ts +2 -2
- package/dist/debugLogger.d.ts.map +1 -1
- package/dist/debugLogger.js.map +1 -1
- package/dist/deltaManager.d.ts +2 -2
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js.map +1 -1
- package/dist/error.d.ts +2 -2
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/frozenServices.d.ts.map +1 -1
- package/dist/frozenServices.js +15 -2
- package/dist/frozenServices.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +3 -0
- package/dist/legacyAlpha.d.ts +3 -0
- package/dist/loaderLayerCompatState.d.ts +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +2 -2
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +13 -1
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts +2 -2
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +3 -3
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +6 -0
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +3 -3
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +2 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/createAndLoadContainerUtils.d.ts +111 -19
- package/lib/createAndLoadContainerUtils.d.ts.map +1 -1
- package/lib/createAndLoadContainerUtils.js +85 -3
- package/lib/createAndLoadContainerUtils.js.map +1 -1
- package/lib/debugLogger.d.ts +2 -2
- package/lib/debugLogger.d.ts.map +1 -1
- package/lib/debugLogger.js.map +1 -1
- package/lib/deltaManager.d.ts +2 -2
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js.map +1 -1
- package/lib/error.d.ts +2 -2
- package/lib/error.d.ts.map +1 -1
- package/lib/error.js.map +1 -1
- package/lib/frozenServices.d.ts.map +1 -1
- package/lib/frozenServices.js +15 -2
- package/lib/frozenServices.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +3 -0
- package/lib/legacyAlpha.d.ts +3 -0
- package/lib/loaderLayerCompatState.d.ts +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +2 -2
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +13 -1
- package/lib/utils.js.map +1 -1
- package/package.json +11 -11
- package/src/connectionManager.ts +2 -2
- package/src/connectionStateHandler.ts +3 -3
- package/src/container.ts +7 -0
- package/src/containerContext.ts +3 -3
- package/src/containerStorageAdapter.ts +3 -3
- package/src/createAndLoadContainerUtils.ts +227 -24
- package/src/debugLogger.ts +2 -2
- package/src/deltaManager.ts +3 -3
- package/src/error.ts +2 -2
- package/src/frozenServices.ts +18 -2
- package/src/index.ts +3 -0
- package/src/packageVersion.ts +1 -1
- package/src/retriableDocumentStorageService.ts +2 -2
- package/src/utils.ts +12 -1
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
IVersion,
|
|
25
25
|
} from "@fluidframework/driver-definitions/internal";
|
|
26
26
|
import { isInstanceOfISnapshot, UsageError } from "@fluidframework/driver-utils/internal";
|
|
27
|
-
import type {
|
|
27
|
+
import type { TelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
28
28
|
|
|
29
29
|
import type { MemoryDetachedBlobStorage } from "./memoryBlobStorage.js";
|
|
30
30
|
import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService.js";
|
|
@@ -107,7 +107,7 @@ export class ContainerStorageAdapter
|
|
|
107
107
|
*/
|
|
108
108
|
public constructor(
|
|
109
109
|
detachedBlobStorage: MemoryDetachedBlobStorage | undefined,
|
|
110
|
-
private readonly logger:
|
|
110
|
+
private readonly logger: TelemetryLoggerExt,
|
|
111
111
|
private loadingGroupIdSnapshotsFromPendingState:
|
|
112
112
|
| Record<string, SerializedSnapshotInfo>
|
|
113
113
|
| undefined,
|
|
@@ -276,7 +276,7 @@ export class ContainerStorageAdapter
|
|
|
276
276
|
class BlobOnlyStorage implements IDocumentStorageService {
|
|
277
277
|
constructor(
|
|
278
278
|
private readonly detachedStorage: MemoryDetachedBlobStorage | undefined,
|
|
279
|
-
private readonly logger:
|
|
279
|
+
private readonly logger: TelemetryLoggerExt,
|
|
280
280
|
) {}
|
|
281
281
|
|
|
282
282
|
public async createBlob(content: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
@@ -16,9 +16,12 @@ import type {
|
|
|
16
16
|
IRequest,
|
|
17
17
|
ITelemetryBaseLogger,
|
|
18
18
|
} from "@fluidframework/core-interfaces";
|
|
19
|
+
import type { AllOrNone } from "@fluidframework/core-interfaces/internal";
|
|
20
|
+
import { validateAllOrNone } from "@fluidframework/core-utils/internal";
|
|
19
21
|
import type { IClientDetails } from "@fluidframework/driver-definitions";
|
|
20
22
|
import type {
|
|
21
23
|
IDocumentServiceFactory,
|
|
24
|
+
IResolvedUrl,
|
|
22
25
|
ISequencedDocumentMessage,
|
|
23
26
|
ISnapshot,
|
|
24
27
|
ISnapshotTree,
|
|
@@ -59,7 +62,11 @@ import type {
|
|
|
59
62
|
OnDemandSummaryResults,
|
|
60
63
|
SummarizeOnDemandResults,
|
|
61
64
|
} from "./summarizerResultTypes.js";
|
|
62
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
getAttachedContainerStateFromSerializedContainer,
|
|
67
|
+
getDocumentAttributes,
|
|
68
|
+
tryParseCompatibleResolvedUrl,
|
|
69
|
+
} from "./utils.js";
|
|
63
70
|
|
|
64
71
|
interface OnDemandSummarizeResultsPromises {
|
|
65
72
|
readonly summarySubmitted: Promise<SummarizeOnDemandResults["summarySubmitted"]>;
|
|
@@ -78,22 +85,17 @@ interface SummarizerLike {
|
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
/**
|
|
81
|
-
*
|
|
88
|
+
* Host-level container loader properties — the code loader plus all the
|
|
89
|
+
* optional policy / observability fields that aren't tied to driver wiring.
|
|
90
|
+
*
|
|
91
|
+
* @remarks
|
|
92
|
+
* Extracted as a reusable building block so other props types (create,
|
|
93
|
+
* rehydrate, load, frozen-load) can compose it without duplicating the
|
|
94
|
+
* optional-fields surface.
|
|
95
|
+
*
|
|
82
96
|
* @legacy @beta
|
|
83
97
|
*/
|
|
84
|
-
export interface
|
|
85
|
-
/**
|
|
86
|
-
* The url resolver used by the loader for resolving external urls
|
|
87
|
-
* into Fluid urls such that the container specified by the
|
|
88
|
-
* external url can be loaded.
|
|
89
|
-
*/
|
|
90
|
-
readonly urlResolver: IUrlResolver;
|
|
91
|
-
/**
|
|
92
|
-
* The document service factory take the Fluid url provided
|
|
93
|
-
* by the resolved url and constructs all the necessary services
|
|
94
|
-
* for communication with the container's server.
|
|
95
|
-
*/
|
|
96
|
-
readonly documentServiceFactory: IDocumentServiceFactory;
|
|
98
|
+
export interface IContainerHostProps {
|
|
97
99
|
/**
|
|
98
100
|
* The code loader handles loading the necessary code
|
|
99
101
|
* for running a container once it is loaded.
|
|
@@ -139,11 +141,75 @@ export interface ICreateAndLoadContainerProps {
|
|
|
139
141
|
readonly clientDetailsOverride?: IClientDetails | undefined;
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
/**
|
|
145
|
+
* The driver-services pair — `urlResolver` plus `documentServiceFactory` —
|
|
146
|
+
* required to wire a container to a real driver at create or load time.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* Extracted as a reusable building block so the `request` field can be
|
|
150
|
+
* added on top for load-time props (see {@link IContainerLoadDriverProps})
|
|
151
|
+
* while create-time props that don't carry a request can compose just this
|
|
152
|
+
* pair.
|
|
153
|
+
*
|
|
154
|
+
* @legacy @beta
|
|
155
|
+
*/
|
|
156
|
+
export interface IContainerDriverServices {
|
|
157
|
+
/**
|
|
158
|
+
* The url resolver used by the loader for resolving external urls
|
|
159
|
+
* into Fluid urls such that the container specified by the
|
|
160
|
+
* external url can be loaded.
|
|
161
|
+
*/
|
|
162
|
+
readonly urlResolver: IUrlResolver;
|
|
163
|
+
/**
|
|
164
|
+
* The document service factory take the Fluid url provided
|
|
165
|
+
* by the resolved url and constructs all the necessary services
|
|
166
|
+
* for communication with the container's server.
|
|
167
|
+
*/
|
|
168
|
+
readonly documentServiceFactory: IDocumentServiceFactory;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The load-time driver wiring trio — `request`, `urlResolver`, and
|
|
173
|
+
* `documentServiceFactory` together.
|
|
174
|
+
*
|
|
175
|
+
* @remarks
|
|
176
|
+
* Reused as the all-or-nothing group for entry points (e.g. the frozen-load
|
|
177
|
+
* entry point) that accept either a full driver wiring (online form) or none
|
|
178
|
+
* of it (offline form). See `AllOrNone` in `@fluidframework/core-interfaces`.
|
|
179
|
+
*
|
|
180
|
+
* @legacy @beta
|
|
181
|
+
*/
|
|
182
|
+
export interface IContainerLoadDriverProps extends IContainerDriverServices {
|
|
183
|
+
/**
|
|
184
|
+
* The request to resolve the container.
|
|
185
|
+
*/
|
|
186
|
+
readonly request: IRequest;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Properties necessary for creating and loading a container.
|
|
191
|
+
*
|
|
192
|
+
* @deprecated
|
|
193
|
+
* Use the composable building blocks instead: extend
|
|
194
|
+
* {@link IContainerHostProps} for the code-loader / policy / observability
|
|
195
|
+
* surface and {@link IContainerDriverServices} for the
|
|
196
|
+
* `urlResolver` / `documentServiceFactory` pair, or compose them inline as
|
|
197
|
+
* `IContainerHostProps & IContainerDriverServices`. This interface is kept
|
|
198
|
+
* as an alias for back-compat and will be removed in a future release.
|
|
199
|
+
*
|
|
200
|
+
* @legacy @beta
|
|
201
|
+
*/
|
|
202
|
+
export interface ICreateAndLoadContainerProps
|
|
203
|
+
extends IContainerHostProps,
|
|
204
|
+
IContainerDriverServices {}
|
|
205
|
+
|
|
142
206
|
/**
|
|
143
207
|
* Props used to load a container.
|
|
144
208
|
* @legacy @beta
|
|
145
209
|
*/
|
|
146
|
-
export interface ILoadExistingContainerProps
|
|
210
|
+
export interface ILoadExistingContainerProps
|
|
211
|
+
extends IContainerHostProps,
|
|
212
|
+
IContainerDriverServices {
|
|
147
213
|
/**
|
|
148
214
|
* The request to resolve the container.
|
|
149
215
|
*/
|
|
@@ -168,7 +234,9 @@ export type ILoadSummarizerContainerProps = Omit<
|
|
|
168
234
|
* Props used to create a detached container.
|
|
169
235
|
* @legacy @beta
|
|
170
236
|
*/
|
|
171
|
-
export interface ICreateDetachedContainerProps
|
|
237
|
+
export interface ICreateDetachedContainerProps
|
|
238
|
+
extends IContainerHostProps,
|
|
239
|
+
IContainerDriverServices {
|
|
172
240
|
/**
|
|
173
241
|
* The code details for the container to be created.
|
|
174
242
|
*/
|
|
@@ -179,7 +247,9 @@ export interface ICreateDetachedContainerProps extends ICreateAndLoadContainerPr
|
|
|
179
247
|
* Props used to rehydrate a detached container.
|
|
180
248
|
* @legacy @beta
|
|
181
249
|
*/
|
|
182
|
-
export interface IRehydrateDetachedContainerProps
|
|
250
|
+
export interface IRehydrateDetachedContainerProps
|
|
251
|
+
extends IContainerHostProps,
|
|
252
|
+
IContainerDriverServices {
|
|
183
253
|
/**
|
|
184
254
|
* The serialized state returned by calling serialize on another container
|
|
185
255
|
*/
|
|
@@ -238,10 +308,47 @@ export async function loadExistingContainer(
|
|
|
238
308
|
|
|
239
309
|
/**
|
|
240
310
|
* Properties required to load a frozen container from pending state.
|
|
311
|
+
*
|
|
312
|
+
* @remarks
|
|
313
|
+
* Two forms are supported and selected by the presence of the driver-wiring
|
|
314
|
+
* fields. **Online** form supplies `request`, `urlResolver`, and
|
|
315
|
+
* `documentServiceFactory`; the supplied factory is wrapped in a frozen
|
|
316
|
+
* factory and the resolver is used to re-resolve the request URL just as with
|
|
317
|
+
* {@link loadExistingContainer}. **Offline** form omits all three driver
|
|
318
|
+
* fields; the captured URL stored in `pendingLocalState` is used to
|
|
319
|
+
* synthesize a resolved URL, no real driver is contacted, and
|
|
320
|
+
* `IContainer.getAbsoluteUrl` throws on the returned container because the
|
|
321
|
+
* resolver's external URL format is unknown without a real `IUrlResolver`.
|
|
322
|
+
*
|
|
323
|
+
* **Offline form precondition.** With no driver wiring there is no live
|
|
324
|
+
* storage to read attachment blobs from, so any blob the runtime dereferences
|
|
325
|
+
* during load must already be inlined into `pendingLocalState`. The pending
|
|
326
|
+
* state must therefore be produced by {@link captureFullContainerState}
|
|
327
|
+
* (which inlines all referenced attachment blobs) rather than
|
|
328
|
+
* `IContainer.getPendingLocalState` / `getRequiredPendingLocalState` (which
|
|
329
|
+
* do not). If the runtime needs an attachment blob that is not inlined, the
|
|
330
|
+
* load fails with `UsageError` from the synthesized storage service.
|
|
331
|
+
*
|
|
332
|
+
* **URL shape requirement.** In the offline form the captured
|
|
333
|
+
* `pendingLocalState.url` is the only URL available; it is parsed in place of
|
|
334
|
+
* a real `IUrlResolver.resolve()` call, so it must satisfy
|
|
335
|
+
* {@link tryParseCompatibleResolvedUrl}'s contract — a resolved URL of shape
|
|
336
|
+
* `protocol://<string>/.../..?<querystring>`. This is the format that
|
|
337
|
+
* Fluid-shipped drivers emit; drivers that emit a non-standard resolved-URL
|
|
338
|
+
* shape will surface as a `UsageError` at load time. The online form has no
|
|
339
|
+
* such constraint because the supplied resolver controls URL parsing.
|
|
340
|
+
*
|
|
341
|
+
* The offline form supports `readOnly: false` for the same capture-and-relay
|
|
342
|
+
* use case as the online form: local DDS submissions accrue in the runtime's
|
|
343
|
+
* pending-state manager and can be captured via `getPendingLocalState` for
|
|
344
|
+
* a later online replay. Nothing is published from an offline container.
|
|
345
|
+
*
|
|
346
|
+
* Mixing the two forms (some driver fields supplied, others omitted) is a
|
|
347
|
+
* compile-time error courtesy of the `AllOrNone` modifier from
|
|
348
|
+
* `@fluidframework/core-interfaces`.
|
|
241
349
|
* @legacy @alpha
|
|
242
350
|
*/
|
|
243
|
-
export
|
|
244
|
-
extends ILoadExistingContainerProps {
|
|
351
|
+
export type ILoadFrozenContainerFromPendingStateProps = IContainerHostProps & {
|
|
245
352
|
/**
|
|
246
353
|
* Pending local state to be applied to the container.
|
|
247
354
|
*/
|
|
@@ -274,7 +381,17 @@ export interface ILoadFrozenContainerFromPendingStateProps
|
|
|
274
381
|
* that the runtime accepts DDS submissions and accumulates them in `pendingStateManager`.
|
|
275
382
|
*/
|
|
276
383
|
readonly readOnly?: boolean;
|
|
277
|
-
}
|
|
384
|
+
} & AllOrNone<IContainerLoadDriverProps>;
|
|
385
|
+
|
|
386
|
+
const driverPropKeys = ["request", "urlResolver", "documentServiceFactory"] as const;
|
|
387
|
+
|
|
388
|
+
// Recognizable opaque placeholder used as `request.url` for the offline form of
|
|
389
|
+
// `loadFrozenContainerFromPendingState`. The synthesized `IUrlResolver` ignores
|
|
390
|
+
// its argument and returns a `IResolvedUrl` derived from `pendingLocalState.url`,
|
|
391
|
+
// so this string is never interpreted as a real URL by any downstream stage; it
|
|
392
|
+
// only needs to be a fixed sentinel that won't collide with real request URLs.
|
|
393
|
+
const offlineFrozenRequestPlaceholderUrl =
|
|
394
|
+
"frozen-load-from-pending-state://offline-placeholder";
|
|
278
395
|
|
|
279
396
|
/**
|
|
280
397
|
* Loads a frozen container from pending local state.
|
|
@@ -284,11 +401,84 @@ export interface ILoadFrozenContainerFromPendingStateProps
|
|
|
284
401
|
export async function loadFrozenContainerFromPendingState(
|
|
285
402
|
props: ILoadFrozenContainerFromPendingStateProps,
|
|
286
403
|
): Promise<IContainer> {
|
|
404
|
+
const fnName = loadFrozenContainerFromPendingState.name;
|
|
405
|
+
const { readOnly, pendingLocalState } = props;
|
|
406
|
+
|
|
407
|
+
const driverWiring = validateAllOrNone<IContainerLoadDriverProps>(props, driverPropKeys);
|
|
408
|
+
|
|
409
|
+
if (driverWiring === "mixed") {
|
|
410
|
+
throw new UsageError(
|
|
411
|
+
`${fnName}: ${driverPropKeys.join(", ")} must all be provided or all omitted`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (driverWiring === "none") {
|
|
416
|
+
// Offline: synthesize the driver wiring from the URL captured in pending state.
|
|
417
|
+
// The container's load pipeline is reused unchanged — the synthesized resolver
|
|
418
|
+
// returns a resolved URL whose `url` equals `pendingLocalState.url`, so the
|
|
419
|
+
// identity guard in `Loader.resolveCore` is trivially satisfied.
|
|
420
|
+
const pending = getAttachedContainerStateFromSerializedContainer(pendingLocalState);
|
|
421
|
+
const parsed = tryParseCompatibleResolvedUrl(pending.url);
|
|
422
|
+
if (parsed === undefined) {
|
|
423
|
+
throw new UsageError(
|
|
424
|
+
`${fnName}: pending state URL is not in a parseable form (${pending.url})`,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
// `tryParseCompatibleResolvedUrl` returns `parsed.id` as the first two
|
|
428
|
+
// path segments joined by `/` (i.e. `tenantId/docId`). Production
|
|
429
|
+
// resolvers populate `IResolvedUrl.id` with the single doc-id segment
|
|
430
|
+
// (see `localResolver.ts`, `insecureUrlResolver.ts`,
|
|
431
|
+
// `routerlicious-urlResolver/urlResolver.ts`, `odspDriverUrlResolver.ts`),
|
|
432
|
+
// so downstream consumers reading `IContainer.resolvedUrl.id` for
|
|
433
|
+
// telemetry / cache keys expect the single-segment shape. Take the
|
|
434
|
+
// trailing segment so offline-loaded containers match that contract.
|
|
435
|
+
const parsedIdSegments = parsed.id.split("/");
|
|
436
|
+
const synthesizedId = parsedIdSegments[parsedIdSegments.length - 1] ?? parsed.id;
|
|
437
|
+
const synthesizedResolvedUrl: IResolvedUrl = {
|
|
438
|
+
type: "fluid",
|
|
439
|
+
id: synthesizedId,
|
|
440
|
+
url: pending.url,
|
|
441
|
+
// `tokens` and `endpoints` are empty because no real driver is contacted; the
|
|
442
|
+
// frozen factory never reads them. A future offline mode that needs them would
|
|
443
|
+
// require carrying them in the pending-state format.
|
|
444
|
+
tokens: {},
|
|
445
|
+
endpoints: {},
|
|
446
|
+
};
|
|
447
|
+
const synthesizedUrlResolver: IUrlResolver = {
|
|
448
|
+
// The argument is ignored: in offline mode the only URL in the system is the
|
|
449
|
+
// one captured in pending state. Any request is mapped to the same resolved URL.
|
|
450
|
+
resolve: async () => synthesizedResolvedUrl,
|
|
451
|
+
// External (request-form) URLs cannot be reconstructed from the captured
|
|
452
|
+
// resolved URL alone, so we cannot honor `getAbsoluteUrl`. Surface the misuse
|
|
453
|
+
// loudly rather than fabricate a string in the wrong format.
|
|
454
|
+
getAbsoluteUrl: async () => {
|
|
455
|
+
throw new UsageError(`${fnName}: getAbsoluteUrl requires ${driverPropKeys.join("/")}`);
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
return loadExistingContainer({
|
|
459
|
+
...props,
|
|
460
|
+
// `request.url` is unused: `synthesizedUrlResolver.resolve()` returns
|
|
461
|
+
// `synthesizedResolvedUrl` regardless of input, and the identity
|
|
462
|
+
// guard in `Loader.resolveCore` compares the resolver's output URL
|
|
463
|
+
// against `pendingLocalState.url` — both equal `pending.url` here.
|
|
464
|
+
// Using a recognizable opaque placeholder instead of the resolved-form
|
|
465
|
+
// URL avoids implying that any downstream stage interprets it as a
|
|
466
|
+
// request-form URL.
|
|
467
|
+
request: { url: offlineFrozenRequestPlaceholderUrl, headers: {} },
|
|
468
|
+
urlResolver: synthesizedUrlResolver,
|
|
469
|
+
documentServiceFactory: createFrozenDocumentServiceFactory(undefined, readOnly),
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Online: wrap the caller-supplied factory so the live driver only serves blob reads.
|
|
474
|
+
// `driverWiring === "all"` narrows `props` to the form that carries the driver fields.
|
|
475
|
+
const onlineProps = props as ILoadFrozenContainerFromPendingStateProps &
|
|
476
|
+
IContainerLoadDriverProps;
|
|
287
477
|
return loadExistingContainer({
|
|
288
|
-
...
|
|
478
|
+
...onlineProps,
|
|
289
479
|
documentServiceFactory: createFrozenDocumentServiceFactory(
|
|
290
|
-
|
|
291
|
-
|
|
480
|
+
onlineProps.documentServiceFactory,
|
|
481
|
+
readOnly,
|
|
292
482
|
),
|
|
293
483
|
});
|
|
294
484
|
}
|
|
@@ -372,6 +562,19 @@ export async function captureFullContainerState({
|
|
|
372
562
|
throw new UsageError("Failed to resolve request to a Fluid URL");
|
|
373
563
|
}
|
|
374
564
|
|
|
565
|
+
// Validate the resolver's URL shape at capture time. The captured pending
|
|
566
|
+
// state is rehydrated later (possibly in a different process) via the
|
|
567
|
+
// offline form of `loadFrozenContainerFromPendingState`, which requires
|
|
568
|
+
// `tryParseCompatibleResolvedUrl` to succeed on `pendingLocalState.url`.
|
|
569
|
+
// Failing fast here turns "your captured artifact silently isn't
|
|
570
|
+
// rehydratable" into a same-call error a partner can act on, instead of
|
|
571
|
+
// surfacing as a `UsageError` at offline-load time in a different process.
|
|
572
|
+
if (tryParseCompatibleResolvedUrl(resolvedUrl.url) === undefined) {
|
|
573
|
+
throw new UsageError(
|
|
574
|
+
`${captureFullContainerState.name}: resolved URL is not in the shape required by tryParseCompatibleResolvedUrl (protocol://<string>/<tenantId>/<docId>?<querystring>); captured state would not rehydrate offline (${resolvedUrl.url})`,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
375
578
|
const documentService = await documentServiceFactory.createDocumentService(
|
|
376
579
|
resolvedUrl,
|
|
377
580
|
logger,
|
package/src/debugLogger.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
ITelemetryBaseProperties,
|
|
11
11
|
} from "@fluidframework/core-interfaces";
|
|
12
12
|
import {
|
|
13
|
-
type
|
|
13
|
+
type TelemetryLoggerExt,
|
|
14
14
|
type ITelemetryLoggerPropertyBags,
|
|
15
15
|
createMultiSinkLogger,
|
|
16
16
|
eventNamespaceSeparator,
|
|
@@ -38,7 +38,7 @@ export class DebugLogger implements ITelemetryBaseLogger {
|
|
|
38
38
|
namespace: string,
|
|
39
39
|
baseLogger?: ITelemetryBaseLogger,
|
|
40
40
|
properties?: ITelemetryLoggerPropertyBags,
|
|
41
|
-
):
|
|
41
|
+
): TelemetryLoggerExt {
|
|
42
42
|
// Setup base logger upfront, such that host can disable it (if needed)
|
|
43
43
|
const debug = registerDebug(namespace);
|
|
44
44
|
|
package/src/deltaManager.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { NonRetryableError, isRuntimeMessage } from "@fluidframework/driver-util
|
|
|
36
36
|
import {
|
|
37
37
|
type ITelemetryErrorEventExt,
|
|
38
38
|
type ITelemetryGenericEventExt,
|
|
39
|
-
type
|
|
39
|
+
type TelemetryLoggerExt,
|
|
40
40
|
DataCorruptionError,
|
|
41
41
|
DataProcessingError,
|
|
42
42
|
UsageError,
|
|
@@ -133,7 +133,7 @@ function isClientMessage(message: ISequencedDocumentMessage | IDocumentMessage):
|
|
|
133
133
|
*/
|
|
134
134
|
function logIfFalse(
|
|
135
135
|
condition: boolean,
|
|
136
|
-
logger:
|
|
136
|
+
logger: TelemetryLoggerExt,
|
|
137
137
|
event: string | ITelemetryGenericEventExt,
|
|
138
138
|
): condition is true {
|
|
139
139
|
if (condition) {
|
|
@@ -420,7 +420,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
420
420
|
|
|
421
421
|
constructor(
|
|
422
422
|
private readonly serviceProvider: () => IDocumentService | undefined,
|
|
423
|
-
private readonly logger:
|
|
423
|
+
private readonly logger: TelemetryLoggerExt,
|
|
424
424
|
private readonly _active: () => boolean,
|
|
425
425
|
createConnectionManager: (props: IConnectionManagerFactoryArgs) => TConnectionManager,
|
|
426
426
|
) {
|
package/src/error.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
|
|
|
8
8
|
import type { IThrottlingWarning } from "@fluidframework/core-interfaces/internal";
|
|
9
9
|
import {
|
|
10
10
|
type IFluidErrorBase,
|
|
11
|
-
type
|
|
11
|
+
type TelemetryLoggerExt,
|
|
12
12
|
LoggingError,
|
|
13
13
|
wrapErrorAndLog,
|
|
14
14
|
} from "@fluidframework/telemetry-utils/internal";
|
|
@@ -40,7 +40,7 @@ export class ThrottlingWarning
|
|
|
40
40
|
public static wrap(
|
|
41
41
|
error: unknown,
|
|
42
42
|
retryAfterSeconds: number,
|
|
43
|
-
logger:
|
|
43
|
+
logger: TelemetryLoggerExt,
|
|
44
44
|
): IThrottlingWarning {
|
|
45
45
|
const newErrorFn = (errMsg: string): ThrottlingWarning =>
|
|
46
46
|
new ThrottlingWarning(errMsg, retryAfterSeconds);
|
package/src/frozenServices.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type ISignalMessage,
|
|
27
27
|
type ITokenClaims,
|
|
28
28
|
} from "@fluidframework/driver-definitions/internal";
|
|
29
|
+
import { UsageError } from "@fluidframework/telemetry-utils/internal";
|
|
29
30
|
import { v4 as uuid } from "uuid";
|
|
30
31
|
|
|
31
32
|
import type { IConnectionStateChangeReason } from "./contracts.js";
|
|
@@ -165,7 +166,22 @@ class FrozenDocumentService
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
const frozenDocumentStorageServiceHandler = (): never => {
|
|
168
|
-
throw new
|
|
169
|
+
throw new UsageError("Operations are not supported on the FrozenDocumentStorageService.");
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Distinct from {@link frozenDocumentStorageServiceHandler} because callers
|
|
174
|
+
* that hit this path are almost always exercising a fully-offline frozen load
|
|
175
|
+
* whose pending state was produced by {@link getPendingLocalState} (which omits
|
|
176
|
+
* inlined attachment blobs) rather than {@link captureFullContainerState}. A
|
|
177
|
+
* generic "operation not supported" is technically true but unhelpful; this
|
|
178
|
+
* message names the missing precondition and the API that produces it.
|
|
179
|
+
*/
|
|
180
|
+
const frozenReadBlobOfflineHandler = async (): Promise<never> => {
|
|
181
|
+
throw new UsageError(
|
|
182
|
+
"Attempted to read an attachment blob from a frozen-loaded container without a live storage service. " +
|
|
183
|
+
"Fully-offline frozen loads must use pending state produced by `captureFullContainerState`, which inlines all referenced attachment blobs.",
|
|
184
|
+
);
|
|
169
185
|
};
|
|
170
186
|
|
|
171
187
|
class FrozenDocumentStorageService implements IDocumentStorageService, IDisposable {
|
|
@@ -217,7 +233,7 @@ class FrozenDocumentStorageService implements IDocumentStorageService, IDisposab
|
|
|
217
233
|
createBlob: IDocumentStorageService["createBlob"];
|
|
218
234
|
readBlob =
|
|
219
235
|
this.documentStorageService?.readBlob.bind(this.documentStorageService) ??
|
|
220
|
-
|
|
236
|
+
frozenReadBlobOfflineHandler;
|
|
221
237
|
uploadSummaryWithContext = frozenDocumentStorageServiceHandler;
|
|
222
238
|
downloadSummary = frozenDocumentStorageServiceHandler;
|
|
223
239
|
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,9 @@ export {
|
|
|
14
14
|
loadFrozenContainerFromPendingState,
|
|
15
15
|
loadSummarizerContainerAndMakeSummary,
|
|
16
16
|
type ICaptureFullContainerStateProps,
|
|
17
|
+
type IContainerDriverServices,
|
|
18
|
+
type IContainerHostProps,
|
|
19
|
+
type IContainerLoadDriverProps,
|
|
17
20
|
type ICreateAndLoadContainerProps,
|
|
18
21
|
type ICreateDetachedContainerProps,
|
|
19
22
|
type ILoadExistingContainerProps,
|
package/src/packageVersion.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
} from "@fluidframework/driver-definitions/internal";
|
|
20
20
|
import { runWithRetry } from "@fluidframework/driver-utils/internal";
|
|
21
21
|
import {
|
|
22
|
-
type
|
|
22
|
+
type TelemetryLoggerExt,
|
|
23
23
|
GenericError,
|
|
24
24
|
UsageError,
|
|
25
25
|
} from "@fluidframework/telemetry-utils/internal";
|
|
@@ -29,7 +29,7 @@ export class RetriableDocumentStorageService implements IDocumentStorageService,
|
|
|
29
29
|
private internalStorageService: IDocumentStorageService | undefined;
|
|
30
30
|
constructor(
|
|
31
31
|
private readonly internalStorageServiceP: Promise<IDocumentStorageService>,
|
|
32
|
-
private readonly logger:
|
|
32
|
+
private readonly logger: TelemetryLoggerExt,
|
|
33
33
|
private readonly maxRetries?: number,
|
|
34
34
|
) {
|
|
35
35
|
this.internalStorageServiceP
|
package/src/utils.ts
CHANGED
|
@@ -81,7 +81,18 @@ export interface IParsedUrl {
|
|
|
81
81
|
* @legacy @beta
|
|
82
82
|
*/
|
|
83
83
|
export function tryParseCompatibleResolvedUrl(url: string): IParsedUrl | undefined {
|
|
84
|
-
|
|
84
|
+
// `new URL(...)` throws `TypeError` for any string that isn't a well-formed
|
|
85
|
+
// absolute URL. The `try*` name in this function's identifier implies a
|
|
86
|
+
// non-throwing contract on bad input, and callers all gate on `=== undefined`
|
|
87
|
+
// to surface their own errors — letting the built-in throw escape here would
|
|
88
|
+
// bypass those caller-supplied error messages for the broadest class of bad
|
|
89
|
+
// URLs ("not absolute", "invalid characters", etc.).
|
|
90
|
+
let parsed: URL;
|
|
91
|
+
try {
|
|
92
|
+
parsed = new URL(url);
|
|
93
|
+
} catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
85
96
|
if (typeof parsed.pathname !== "string") {
|
|
86
97
|
throw new LoggingError("Failed to parse pathname");
|
|
87
98
|
}
|