@fluidframework/container-loader 2.63.0-359734 → 2.63.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 +4 -0
- package/api-report/container-loader.legacy.alpha.api.md +31 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +12 -8
- package/dist/container.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +7 -13
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +9 -14
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/createAndLoadContainerUtils.d.ts +13 -0
- package/dist/createAndLoadContainerUtils.d.ts.map +1 -1
- package/dist/createAndLoadContainerUtils.js +118 -2
- package/dist/createAndLoadContainerUtils.js.map +1 -1
- package/dist/frozenServices.d.ts +10 -1
- package/dist/frozenServices.d.ts.map +1 -1
- package/dist/frozenServices.js +24 -4
- package/dist/frozenServices.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/legacyAlpha.d.ts +7 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/serializedStateManager.d.ts +10 -6
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js +76 -77
- package/dist/serializedStateManager.js.map +1 -1
- package/dist/summarizerResultTypes.d.ts +96 -0
- package/dist/summarizerResultTypes.d.ts.map +1 -0
- package/dist/summarizerResultTypes.js +9 -0
- package/dist/summarizerResultTypes.js.map +1 -0
- package/dist/utils.d.ts +4 -5
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +14 -13
- package/dist/utils.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +13 -9
- package/lib/container.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +7 -13
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +10 -15
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/createAndLoadContainerUtils.d.ts +13 -0
- package/lib/createAndLoadContainerUtils.d.ts.map +1 -1
- package/lib/createAndLoadContainerUtils.js +117 -2
- package/lib/createAndLoadContainerUtils.js.map +1 -1
- package/lib/frozenServices.d.ts +10 -1
- package/lib/frozenServices.d.ts.map +1 -1
- package/lib/frozenServices.js +20 -1
- package/lib/frozenServices.js.map +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/legacyAlpha.d.ts +7 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/serializedStateManager.d.ts +10 -6
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js +78 -79
- package/lib/serializedStateManager.js.map +1 -1
- package/lib/summarizerResultTypes.d.ts +96 -0
- package/lib/summarizerResultTypes.d.ts.map +1 -0
- package/lib/summarizerResultTypes.js +6 -0
- package/lib/summarizerResultTypes.js.map +1 -0
- package/lib/utils.d.ts +4 -5
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +15 -14
- package/lib/utils.js.map +1 -1
- package/package.json +11 -11
- package/src/container.ts +19 -14
- package/src/containerStorageAdapter.ts +14 -18
- package/src/createAndLoadContainerUtils.ts +176 -2
- package/src/frozenServices.ts +28 -2
- package/src/index.ts +8 -0
- package/src/packageVersion.ts +1 -1
- package/src/serializedStateManager.ts +118 -109
- package/src/summarizerResultTypes.ts +115 -0
- package/src/utils.ts +20 -20
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type IVersion,
|
|
23
23
|
type ISequencedDocumentMessage,
|
|
24
24
|
} from "@fluidframework/driver-definitions/internal";
|
|
25
|
-
import { getSnapshotTree } from "@fluidframework/driver-utils/internal";
|
|
25
|
+
import { getSnapshotTree, isInstanceOfISnapshot } from "@fluidframework/driver-utils/internal";
|
|
26
26
|
import {
|
|
27
27
|
type MonitoringContext,
|
|
28
28
|
PerformanceEvent,
|
|
@@ -31,12 +31,13 @@ import {
|
|
|
31
31
|
} from "@fluidframework/telemetry-utils/internal";
|
|
32
32
|
|
|
33
33
|
import {
|
|
34
|
-
type ISerializableBlobContents,
|
|
35
34
|
getBlobContentsFromTree,
|
|
35
|
+
type ContainerStorageAdapter,
|
|
36
|
+
type ISerializableBlobContents,
|
|
36
37
|
} from "./containerStorageAdapter.js";
|
|
37
38
|
import {
|
|
38
|
-
convertSnapshotToSnapshotInfo,
|
|
39
39
|
convertISnapshotToSnapshotWithBlobs,
|
|
40
|
+
convertSnapshotToSnapshotInfo,
|
|
40
41
|
getDocumentAttributes,
|
|
41
42
|
} from "./utils.js";
|
|
42
43
|
|
|
@@ -80,7 +81,7 @@ export interface IPendingContainerState extends SnapshotWithBlobs {
|
|
|
80
81
|
/**
|
|
81
82
|
* Any group snapshots (aka delay-loaded) we've downloaded from the service for this container
|
|
82
83
|
*/
|
|
83
|
-
loadedGroupIdSnapshots?: Record<string,
|
|
84
|
+
loadedGroupIdSnapshots?: Record<string, SerializedSnapshotInfo>;
|
|
84
85
|
/**
|
|
85
86
|
* All ops since base snapshot sequence number up to the latest op
|
|
86
87
|
* seen when the container was closed. Used to apply stashed (saved pending)
|
|
@@ -122,14 +123,19 @@ export interface IPendingDetachedContainerState extends SnapshotWithBlobs {
|
|
|
122
123
|
pendingRuntimeState?: unknown;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
export interface
|
|
126
|
+
export interface SerializedSnapshotInfo extends SnapshotWithBlobs {
|
|
127
|
+
snapshotSequenceNumber: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface ISnapshotInfo {
|
|
126
131
|
snapshotSequenceNumber: number;
|
|
127
132
|
snapshotFetchedTime?: number | undefined;
|
|
133
|
+
snapshot: ISnapshot | ISnapshotTree;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
export type ISerializedStateManagerDocumentStorageService = Pick<
|
|
131
|
-
|
|
132
|
-
"getSnapshot" | "getSnapshotTree" | "getVersions" | "readBlob"
|
|
137
|
+
ContainerStorageAdapter,
|
|
138
|
+
"getSnapshot" | "getSnapshotTree" | "getVersions" | "readBlob" | "cacheSnapshotBlobs"
|
|
133
139
|
> & {
|
|
134
140
|
loadedGroupIdSnapshots: Record<string, ISnapshot>;
|
|
135
141
|
};
|
|
@@ -169,7 +175,7 @@ class RefreshPromiseTracker {
|
|
|
169
175
|
export class SerializedStateManager implements IDisposable {
|
|
170
176
|
private readonly processedOps: ISequencedDocumentMessage[] = [];
|
|
171
177
|
private readonly mc: MonitoringContext;
|
|
172
|
-
private
|
|
178
|
+
private snapshotInfo: ISnapshotInfo | undefined;
|
|
173
179
|
private latestSnapshot: ISnapshotInfo | undefined;
|
|
174
180
|
private readonly refreshTracker = new RefreshPromiseTracker(
|
|
175
181
|
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
@@ -211,6 +217,7 @@ export class SerializedStateManager implements IDisposable {
|
|
|
211
217
|
this.snapshotRefreshTimeoutMs = snapshotRefreshTimeoutMs ?? this.snapshotRefreshTimeoutMs;
|
|
212
218
|
|
|
213
219
|
this.#snapshotRefreshEnabled =
|
|
220
|
+
_offlineLoadEnabled &&
|
|
214
221
|
(this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") ??
|
|
215
222
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineFull")) === true;
|
|
216
223
|
|
|
@@ -270,68 +277,62 @@ export class SerializedStateManager implements IDisposable {
|
|
|
270
277
|
specifiedVersion: string | undefined,
|
|
271
278
|
pendingLocalState: IPendingContainerState | undefined,
|
|
272
279
|
): Promise<{
|
|
273
|
-
|
|
280
|
+
snapshot: ISnapshot | ISnapshotTree;
|
|
274
281
|
version: IVersion | undefined;
|
|
275
282
|
attributes: IDocumentAttributes;
|
|
276
283
|
}> {
|
|
277
284
|
this.verifyNotDisposed();
|
|
278
285
|
if (pendingLocalState === undefined) {
|
|
279
|
-
const {
|
|
286
|
+
const { snapshot, version } = await getSnapshot(
|
|
280
287
|
this.mc,
|
|
281
288
|
this.storageAdapter,
|
|
282
289
|
this.supportGetSnapshotApi(),
|
|
283
290
|
specifiedVersion,
|
|
284
291
|
);
|
|
285
|
-
const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(
|
|
292
|
+
const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(snapshot);
|
|
286
293
|
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshotTree);
|
|
287
|
-
// non-interactive clients will not have any pending state we want to save
|
|
288
294
|
if (this.offlineLoadEnabled) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
baseSnapshot: baseSnapshotTree,
|
|
295
|
-
snapshotBlobs,
|
|
296
|
-
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
297
|
-
};
|
|
298
|
-
this.refreshTimer?.start();
|
|
299
|
-
return attributes.sequenceNumber;
|
|
300
|
-
}),
|
|
301
|
-
);
|
|
295
|
+
this.refreshTimer?.start();
|
|
296
|
+
this.snapshotInfo = {
|
|
297
|
+
snapshot,
|
|
298
|
+
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
299
|
+
};
|
|
302
300
|
}
|
|
303
|
-
return {
|
|
301
|
+
return { snapshot, version, attributes };
|
|
304
302
|
} else {
|
|
305
303
|
const { baseSnapshot, snapshotBlobs, savedOps } = pendingLocalState;
|
|
306
|
-
|
|
307
|
-
// special case handle. Obtaining the last saved op seq num to avoid
|
|
308
|
-
// refreshing the snapshot before we have processed it. It could cause
|
|
309
|
-
// a subsequent stashing to have a newer snapshot than allowed.
|
|
310
|
-
if (savedOps.length > 0) {
|
|
311
|
-
const savedOpsSize = savedOps.length;
|
|
312
|
-
this.lastSavedOpSequenceNumber = savedOps[savedOpsSize - 1].sequenceNumber;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshot);
|
|
316
|
-
this.snapshot = {
|
|
317
|
-
baseSnapshot,
|
|
318
|
-
snapshotBlobs,
|
|
319
|
-
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
320
|
-
};
|
|
321
|
-
this.tryRefreshSnapshot();
|
|
322
304
|
const blobContents = new Map<string, ArrayBuffer>();
|
|
323
305
|
for (const [id, value] of Object.entries(snapshotBlobs)) {
|
|
324
306
|
blobContents.set(id, stringToBuffer(value, "utf8"));
|
|
325
307
|
}
|
|
326
|
-
|
|
327
|
-
|
|
308
|
+
this.storageAdapter.cacheSnapshotBlobs(blobContents);
|
|
309
|
+
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshot);
|
|
310
|
+
|
|
311
|
+
const snapshot: ISnapshot = {
|
|
312
|
+
sequenceNumber: attributes.sequenceNumber,
|
|
328
313
|
snapshotTree: baseSnapshot,
|
|
329
314
|
blobContents,
|
|
330
315
|
latestSequenceNumber: undefined,
|
|
331
316
|
ops: [],
|
|
332
317
|
snapshotFormatV: 1,
|
|
333
318
|
};
|
|
334
|
-
|
|
319
|
+
|
|
320
|
+
if (this.offlineLoadEnabled) {
|
|
321
|
+
// special case handle. Obtaining the last saved op seq num to avoid
|
|
322
|
+
// refreshing the snapshot before we have processed it. It could cause
|
|
323
|
+
// a subsequent stashing to have a newer snapshot than allowed.
|
|
324
|
+
if (savedOps.length > 0) {
|
|
325
|
+
const savedOpsSize = savedOps.length;
|
|
326
|
+
this.lastSavedOpSequenceNumber = savedOps[savedOpsSize - 1].sequenceNumber;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.snapshotInfo = {
|
|
330
|
+
snapshot,
|
|
331
|
+
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
332
|
+
};
|
|
333
|
+
this.tryRefreshSnapshot();
|
|
334
|
+
}
|
|
335
|
+
return { snapshot, version: undefined, attributes };
|
|
335
336
|
}
|
|
336
337
|
}
|
|
337
338
|
|
|
@@ -415,7 +416,7 @@ export class SerializedStateManager implements IDisposable {
|
|
|
415
416
|
snapshotSequenceNumber,
|
|
416
417
|
firstProcessedOpSequenceNumber,
|
|
417
418
|
lastProcessedOpSequenceNumber,
|
|
418
|
-
stashedSnapshotSequenceNumber: this.
|
|
419
|
+
stashedSnapshotSequenceNumber: this.snapshotInfo?.snapshotSequenceNumber,
|
|
419
420
|
});
|
|
420
421
|
this.latestSnapshot = undefined;
|
|
421
422
|
this.refreshTimer?.restart();
|
|
@@ -423,7 +424,7 @@ export class SerializedStateManager implements IDisposable {
|
|
|
423
424
|
// Snapshot seq num is between the first and last processed op.
|
|
424
425
|
// Remove the ops that are already part of the snapshot
|
|
425
426
|
this.processedOps.splice(0, snapshotSequenceNumber - firstProcessedOpSequenceNumber + 1);
|
|
426
|
-
this.
|
|
427
|
+
this.snapshotInfo = this.latestSnapshot;
|
|
427
428
|
this.latestSnapshot = undefined;
|
|
428
429
|
this.refreshTimer?.restart();
|
|
429
430
|
this.mc.logger.sendTelemetryEvent({
|
|
@@ -446,18 +447,9 @@ export class SerializedStateManager implements IDisposable {
|
|
|
446
447
|
public setInitialSnapshot(snapshot: ISnapshot): void {
|
|
447
448
|
this.verifyNotDisposed();
|
|
448
449
|
if (this.offlineLoadEnabled) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
);
|
|
453
|
-
assert(
|
|
454
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
455
|
-
snapshot.sequenceNumber === 0,
|
|
456
|
-
0x939 /* trying to set a non attachment snapshot */,
|
|
457
|
-
);
|
|
458
|
-
this.snapshot = {
|
|
459
|
-
...convertISnapshotToSnapshotWithBlobs(snapshot),
|
|
460
|
-
snapshotSequenceNumber: snapshot.sequenceNumber,
|
|
450
|
+
this.snapshotInfo = {
|
|
451
|
+
snapshot,
|
|
452
|
+
snapshotSequenceNumber: snapshot.sequenceNumber ?? 0,
|
|
461
453
|
snapshotFetchedTime: Date.now(),
|
|
462
454
|
};
|
|
463
455
|
this.refreshTimer?.start();
|
|
@@ -474,6 +466,9 @@ export class SerializedStateManager implements IDisposable {
|
|
|
474
466
|
resolvedUrl: IResolvedUrl,
|
|
475
467
|
): Promise<string> {
|
|
476
468
|
this.verifyNotDisposed();
|
|
469
|
+
if (!this.offlineLoadEnabled) {
|
|
470
|
+
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
471
|
+
}
|
|
477
472
|
|
|
478
473
|
return PerformanceEvent.timedExecAsync(
|
|
479
474
|
this.mc.logger,
|
|
@@ -488,25 +483,14 @@ export class SerializedStateManager implements IDisposable {
|
|
|
488
483
|
clientId,
|
|
489
484
|
},
|
|
490
485
|
async () => {
|
|
491
|
-
|
|
492
|
-
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
493
|
-
}
|
|
494
|
-
if (this.snapshot === undefined && this.refreshTracker.hasPromise) {
|
|
495
|
-
// we deferred the initial download of the snapshot to not block
|
|
496
|
-
// the container load flow, so if it is not resolved
|
|
497
|
-
// and we don't have a snapshot, we will wait for the download
|
|
498
|
-
// to finish.
|
|
499
|
-
await this.refreshTracker.Promise;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
assert(this.snapshot !== undefined, 0x8e5 /* no base data */);
|
|
486
|
+
assert(this.snapshotInfo !== undefined, 0x8e5 /* no base data */);
|
|
503
487
|
const pendingRuntimeState = await runtime.getPendingLocalState({
|
|
504
488
|
notifyImminentClosure: false,
|
|
505
|
-
snapshotSequenceNumber: this.
|
|
506
|
-
sessionExpiryTimerStarted: this.
|
|
489
|
+
snapshotSequenceNumber: this.snapshotInfo.snapshotSequenceNumber,
|
|
490
|
+
sessionExpiryTimerStarted: this.snapshotInfo.snapshotFetchedTime,
|
|
507
491
|
});
|
|
508
492
|
// This conversion is required because ArrayBufferLike doesn't survive JSON.stringify
|
|
509
|
-
const loadedGroupIdSnapshots = {};
|
|
493
|
+
const loadedGroupIdSnapshots: Record<string, SerializedSnapshotInfo> = {};
|
|
510
494
|
let hasGroupIdSnapshots = false;
|
|
511
495
|
const groupIdSnapshots = Object.entries(this.storageAdapter.loadedGroupIdSnapshots);
|
|
512
496
|
if (groupIdSnapshots.length > 0) {
|
|
@@ -515,11 +499,20 @@ export class SerializedStateManager implements IDisposable {
|
|
|
515
499
|
loadedGroupIdSnapshots[groupId] = convertSnapshotToSnapshotInfo(snapshot);
|
|
516
500
|
}
|
|
517
501
|
}
|
|
502
|
+
|
|
503
|
+
const snapshotWithBlobs: SnapshotWithBlobs = isInstanceOfISnapshot(
|
|
504
|
+
this.snapshotInfo.snapshot,
|
|
505
|
+
)
|
|
506
|
+
? convertISnapshotToSnapshotWithBlobs(this.snapshotInfo.snapshot)
|
|
507
|
+
: await convertSnapshotTreeToSnapshotWithBlobs(
|
|
508
|
+
this.snapshotInfo.snapshot,
|
|
509
|
+
this.storageAdapter,
|
|
510
|
+
);
|
|
511
|
+
|
|
518
512
|
const pendingState: IPendingContainerState = {
|
|
519
513
|
attached: true,
|
|
520
514
|
pendingRuntimeState,
|
|
521
|
-
|
|
522
|
-
snapshotBlobs: this.snapshot.snapshotBlobs,
|
|
515
|
+
...snapshotWithBlobs,
|
|
523
516
|
loadedGroupIdSnapshots: hasGroupIdSnapshots ? loadedGroupIdSnapshots : undefined,
|
|
524
517
|
savedOps: this.processedOps,
|
|
525
518
|
url: resolvedUrl.url,
|
|
@@ -532,6 +525,17 @@ export class SerializedStateManager implements IDisposable {
|
|
|
532
525
|
}
|
|
533
526
|
}
|
|
534
527
|
|
|
528
|
+
async function convertSnapshotTreeToSnapshotWithBlobs(
|
|
529
|
+
snapshot: ISnapshotTree,
|
|
530
|
+
storageAdapter: ISerializedStateManagerDocumentStorageService,
|
|
531
|
+
): Promise<SnapshotWithBlobs> {
|
|
532
|
+
const snapshotBlobs = await getBlobContentsFromTree(snapshot, storageAdapter);
|
|
533
|
+
return {
|
|
534
|
+
baseSnapshot: snapshot,
|
|
535
|
+
snapshotBlobs,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
535
539
|
/**
|
|
536
540
|
* Retrieves the most recent snapshot and returns its info.
|
|
537
541
|
*
|
|
@@ -545,41 +549,46 @@ export async function getLatestSnapshotInfo(
|
|
|
545
549
|
storageAdapter: ISerializedStateManagerDocumentStorageService,
|
|
546
550
|
supportGetSnapshotApi: boolean,
|
|
547
551
|
): Promise<ISnapshotInfo | undefined> {
|
|
548
|
-
return PerformanceEvent.timedExecAsync(
|
|
552
|
+
return PerformanceEvent.timedExecAsync<ISnapshotInfo | undefined>(
|
|
549
553
|
mc.logger,
|
|
550
554
|
{ eventName: "GetLatestSnapshotInfo" },
|
|
551
|
-
async () => {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
555
|
+
async (event) => {
|
|
556
|
+
try {
|
|
557
|
+
// get the latest non cached snapshot version
|
|
558
|
+
const specifiedVersion: IVersion[] = await storageAdapter.getVersions(
|
|
559
|
+
// eslint-disable-next-line unicorn/no-null
|
|
560
|
+
null,
|
|
561
|
+
1,
|
|
562
|
+
"getLatestSnapshotInfo",
|
|
563
|
+
FetchSource.noCache,
|
|
564
|
+
);
|
|
565
|
+
const { snapshot: baseSnapshot } = await getSnapshot(
|
|
566
|
+
mc,
|
|
567
|
+
storageAdapter,
|
|
568
|
+
supportGetSnapshotApi,
|
|
569
|
+
specifiedVersion[0]?.id,
|
|
570
|
+
);
|
|
566
571
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
572
|
+
const { sequenceNumber, snapshotTree } = isInstanceOfISnapshot(baseSnapshot)
|
|
573
|
+
? baseSnapshot
|
|
574
|
+
: { snapshotTree: baseSnapshot, sequenceNumber: undefined };
|
|
575
|
+
|
|
576
|
+
const snapshotSequenceNumber: number =
|
|
577
|
+
sequenceNumber ??
|
|
578
|
+
(await getDocumentAttributes(storageAdapter, snapshotTree).then(
|
|
579
|
+
(a) => a.sequenceNumber,
|
|
580
|
+
));
|
|
581
|
+
return {
|
|
582
|
+
snapshot: baseSnapshot,
|
|
583
|
+
snapshotSequenceNumber,
|
|
584
|
+
snapshotFetchedTime: Date.now(),
|
|
585
|
+
};
|
|
586
|
+
} catch (error) {
|
|
587
|
+
event.cancel(undefined, error);
|
|
588
|
+
}
|
|
589
|
+
return undefined;
|
|
581
590
|
},
|
|
582
|
-
)
|
|
591
|
+
);
|
|
583
592
|
}
|
|
584
593
|
|
|
585
594
|
/**
|
|
@@ -599,12 +608,12 @@ async function getSnapshot(
|
|
|
599
608
|
>,
|
|
600
609
|
supportGetSnapshotApi: boolean,
|
|
601
610
|
specifiedVersion: string | undefined,
|
|
602
|
-
): Promise<{
|
|
611
|
+
): Promise<{ snapshot: ISnapshot | ISnapshotTree; version?: IVersion }> {
|
|
603
612
|
const { snapshot, version } = supportGetSnapshotApi
|
|
604
613
|
? await fetchISnapshot(mc, storageAdapter, specifiedVersion)
|
|
605
614
|
: await fetchISnapshotTree(mc, storageAdapter, specifiedVersion);
|
|
606
615
|
assert(snapshot !== undefined, 0x8e4 /* Snapshot should exist */);
|
|
607
|
-
return {
|
|
616
|
+
return { snapshot, version };
|
|
608
617
|
}
|
|
609
618
|
|
|
610
619
|
/**
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IErrorBase } from "@fluidframework/core-interfaces";
|
|
7
|
+
import type {
|
|
8
|
+
ISequencedDocumentMessage,
|
|
9
|
+
ISummaryAck,
|
|
10
|
+
ISummaryContent,
|
|
11
|
+
ISummaryTree,
|
|
12
|
+
MessageType,
|
|
13
|
+
} from "@fluidframework/driver-definitions/internal";
|
|
14
|
+
|
|
15
|
+
export const summarizerRequestUrl = "_summarizer";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Stages of summary process.
|
|
19
|
+
*
|
|
20
|
+
* Stages:
|
|
21
|
+
*
|
|
22
|
+
* 1. "base" - stopped before the summary tree was even generated, and the result only contains the base data
|
|
23
|
+
*
|
|
24
|
+
* 2. "generate" - the summary tree was generated, and the result will contain that tree + stats
|
|
25
|
+
*
|
|
26
|
+
* 3. "upload" - the summary was uploaded to storage, and the result contains the server-provided handle
|
|
27
|
+
*
|
|
28
|
+
* 4. "submit" - the summarize op was submitted, and the result contains the op client sequence number.
|
|
29
|
+
*
|
|
30
|
+
* @legacy @alpha
|
|
31
|
+
*/
|
|
32
|
+
export type SummaryStage = "base" | "generate" | "upload" | "submit" | "unknown";
|
|
33
|
+
|
|
34
|
+
type OnDemandSummaryStageResult<TSuccess> =
|
|
35
|
+
| {
|
|
36
|
+
readonly success: true;
|
|
37
|
+
readonly data: TSuccess;
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
readonly success: false;
|
|
41
|
+
readonly error: IErrorBase;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
interface ISummaryOpMessage extends ISequencedDocumentMessage {
|
|
45
|
+
type: MessageType.Summarize;
|
|
46
|
+
contents: ISummaryContent;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface ISummaryAckMessage extends ISequencedDocumentMessage {
|
|
50
|
+
type: MessageType.SummaryAck;
|
|
51
|
+
contents: ISummaryAck;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export interface SummarizeOnDemandResults {
|
|
58
|
+
readonly summarySubmitted: OnDemandSummaryStageResult<{
|
|
59
|
+
readonly stage: SummaryStage;
|
|
60
|
+
readonly summaryTree?: ISummaryTree;
|
|
61
|
+
readonly handle?: string;
|
|
62
|
+
}>;
|
|
63
|
+
readonly summaryOpBroadcasted: OnDemandSummaryStageResult<{
|
|
64
|
+
readonly broadcastDuration: number;
|
|
65
|
+
readonly summarizeOp: ISummaryOpMessage;
|
|
66
|
+
}>;
|
|
67
|
+
readonly receivedSummaryAckOrNack: OnDemandSummaryStageResult<{
|
|
68
|
+
readonly summaryAckOp: ISummaryAckMessage;
|
|
69
|
+
readonly ackNackDuration: number;
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Results from an on-demand summary request.
|
|
75
|
+
* @legacy @alpha
|
|
76
|
+
*/
|
|
77
|
+
export interface OnDemandSummaryResults {
|
|
78
|
+
/**
|
|
79
|
+
* True if summary was generated, uploaded, and submitted.
|
|
80
|
+
*/
|
|
81
|
+
readonly summarySubmitted: boolean;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Information about the summary that was submitted, if any.
|
|
85
|
+
*/
|
|
86
|
+
readonly summaryInfo: {
|
|
87
|
+
/**
|
|
88
|
+
* Stage at which summary process ended.
|
|
89
|
+
*/
|
|
90
|
+
readonly stage?: SummaryStage;
|
|
91
|
+
/**
|
|
92
|
+
* Handle of the complete summary.
|
|
93
|
+
*/
|
|
94
|
+
readonly handle?: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* True if summarize op broadcast was observed.
|
|
99
|
+
*/
|
|
100
|
+
readonly summaryOpBroadcasted: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Outcome from {@link loadSummarizerContainerAndMakeSummary}.
|
|
105
|
+
* @legacy @alpha
|
|
106
|
+
*/
|
|
107
|
+
export type LoadSummarizerSummaryResult =
|
|
108
|
+
| {
|
|
109
|
+
readonly success: true;
|
|
110
|
+
readonly summaryResults: OnDemandSummaryResults;
|
|
111
|
+
}
|
|
112
|
+
| {
|
|
113
|
+
readonly success: false;
|
|
114
|
+
readonly error: IErrorBase;
|
|
115
|
+
};
|
package/src/utils.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
Uint8ArrayToArrayBuffer,
|
|
8
7
|
bufferToString,
|
|
9
8
|
stringToBuffer,
|
|
9
|
+
Uint8ArrayToArrayBuffer,
|
|
10
10
|
} from "@fluid-internal/client-utils";
|
|
11
11
|
import { assert, compareArrays, unreachableCase } from "@fluidframework/core-utils/internal";
|
|
12
12
|
import { type ISummaryTree, SummaryType } from "@fluidframework/driver-definitions";
|
|
@@ -34,7 +34,7 @@ import type { ISerializableBlobContents } from "./containerStorageAdapter.js";
|
|
|
34
34
|
import type {
|
|
35
35
|
IPendingContainerState,
|
|
36
36
|
IPendingDetachedContainerState,
|
|
37
|
-
|
|
37
|
+
SerializedSnapshotInfo,
|
|
38
38
|
SnapshotWithBlobs,
|
|
39
39
|
} from "./serializedStateManager.js";
|
|
40
40
|
|
|
@@ -195,7 +195,7 @@ function convertSummaryToISnapshot(
|
|
|
195
195
|
* Note, this assumes the ISnapshot sequence number is defined. Otherwise an assert will be thrown
|
|
196
196
|
* @param snapshot - ISnapshot
|
|
197
197
|
*/
|
|
198
|
-
export function convertSnapshotToSnapshotInfo(snapshot: ISnapshot):
|
|
198
|
+
export function convertSnapshotToSnapshotInfo(snapshot: ISnapshot): SerializedSnapshotInfo {
|
|
199
199
|
assert(
|
|
200
200
|
snapshot.sequenceNumber !== undefined,
|
|
201
201
|
0x93a /* Snapshot sequence number is missing */,
|
|
@@ -219,8 +219,7 @@ export function convertSnapshotToSnapshotInfo(snapshot: ISnapshot): ISnapshotInf
|
|
|
219
219
|
* @param snapshot - ISnapshot
|
|
220
220
|
*/
|
|
221
221
|
export function convertSnapshotInfoToSnapshot(
|
|
222
|
-
snapshotInfo:
|
|
223
|
-
snapshotSequenceNumber: number,
|
|
222
|
+
snapshotInfo: SerializedSnapshotInfo,
|
|
224
223
|
): ISnapshot {
|
|
225
224
|
const blobContents = new Map<string, ArrayBuffer>();
|
|
226
225
|
for (const [blobId, serializedContent] of Object.entries(snapshotInfo.snapshotBlobs)) {
|
|
@@ -230,7 +229,7 @@ export function convertSnapshotInfoToSnapshot(
|
|
|
230
229
|
snapshotTree: snapshotInfo.baseSnapshot,
|
|
231
230
|
blobContents,
|
|
232
231
|
ops: [],
|
|
233
|
-
sequenceNumber: snapshotSequenceNumber,
|
|
232
|
+
sequenceNumber: snapshotInfo.snapshotSequenceNumber,
|
|
234
233
|
latestSequenceNumber: undefined,
|
|
235
234
|
snapshotFormatV: 1,
|
|
236
235
|
};
|
|
@@ -275,33 +274,34 @@ export function getProtocolSnapshotTree(snapshot: ISnapshotTree): ISnapshotTree
|
|
|
275
274
|
return ".protocol" in snapshot.trees ? snapshot.trees[".protocol"] : snapshot;
|
|
276
275
|
}
|
|
277
276
|
|
|
278
|
-
export const combineSnapshotTreeAndSnapshotBlobs = (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
): ISnapshotTreeWithBlobContents => {
|
|
282
|
-
const
|
|
277
|
+
export const combineSnapshotTreeAndSnapshotBlobs = ({
|
|
278
|
+
blobContents,
|
|
279
|
+
snapshotTree,
|
|
280
|
+
}: Pick<ISnapshot, "blobContents" | "snapshotTree">): ISnapshotTreeWithBlobContents => {
|
|
281
|
+
const currentTreeBlobs: { [path: string]: ArrayBufferLike } = {};
|
|
283
282
|
|
|
284
283
|
// Process blobs in the current level
|
|
285
|
-
for (const [, id] of Object.entries(
|
|
286
|
-
|
|
287
|
-
|
|
284
|
+
for (const [, id] of Object.entries(snapshotTree.blobs)) {
|
|
285
|
+
const blob = blobContents.get(id);
|
|
286
|
+
if (blob !== undefined) {
|
|
287
|
+
currentTreeBlobs[id] = blob;
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
// Recursively process trees in the current level
|
|
292
292
|
const trees: { [path: string]: ISnapshotTreeWithBlobContents } = {};
|
|
293
|
-
for (const [path, tree] of Object.entries(
|
|
294
|
-
trees[path] = combineSnapshotTreeAndSnapshotBlobs(tree,
|
|
293
|
+
for (const [path, tree] of Object.entries(snapshotTree.trees)) {
|
|
294
|
+
trees[path] = combineSnapshotTreeAndSnapshotBlobs({ snapshotTree: tree, blobContents });
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
// Create a new snapshot tree with blob contents and processed trees
|
|
298
|
-
const
|
|
299
|
-
...
|
|
300
|
-
blobsContents,
|
|
298
|
+
const snapshot: ISnapshotTreeWithBlobContents = {
|
|
299
|
+
...snapshotTree,
|
|
300
|
+
blobsContents: currentTreeBlobs,
|
|
301
301
|
trees,
|
|
302
302
|
};
|
|
303
303
|
|
|
304
|
-
return
|
|
304
|
+
return snapshot;
|
|
305
305
|
};
|
|
306
306
|
|
|
307
307
|
export function isDeltaStreamConnectionForbiddenError(
|