@fluidframework/container-loader 2.63.0-359461 → 2.63.0-359962

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.
Files changed (77) hide show
  1. package/api-report/container-loader.legacy.alpha.api.md +3 -0
  2. package/dist/attachment.d.ts +2 -7
  3. package/dist/attachment.d.ts.map +1 -1
  4. package/dist/attachment.js +2 -4
  5. package/dist/attachment.js.map +1 -1
  6. package/dist/container.d.ts.map +1 -1
  7. package/dist/container.js +7 -7
  8. package/dist/container.js.map +1 -1
  9. package/dist/containerStorageAdapter.d.ts +2 -2
  10. package/dist/containerStorageAdapter.d.ts.map +1 -1
  11. package/dist/containerStorageAdapter.js +1 -1
  12. package/dist/containerStorageAdapter.js.map +1 -1
  13. package/dist/createAndLoadContainerUtils.js +1 -1
  14. package/dist/createAndLoadContainerUtils.js.map +1 -1
  15. package/dist/frozenServices.d.ts +10 -1
  16. package/dist/frozenServices.d.ts.map +1 -1
  17. package/dist/frozenServices.js +24 -4
  18. package/dist/frozenServices.js.map +1 -1
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/legacyAlpha.d.ts +1 -0
  24. package/dist/packageVersion.d.ts +1 -1
  25. package/dist/packageVersion.js +1 -1
  26. package/dist/packageVersion.js.map +1 -1
  27. package/dist/serializedStateManager.d.ts +19 -12
  28. package/dist/serializedStateManager.d.ts.map +1 -1
  29. package/dist/serializedStateManager.js +112 -98
  30. package/dist/serializedStateManager.js.map +1 -1
  31. package/dist/utils.d.ts +10 -4
  32. package/dist/utils.d.ts.map +1 -1
  33. package/dist/utils.js +43 -25
  34. package/dist/utils.js.map +1 -1
  35. package/lib/attachment.d.ts +2 -7
  36. package/lib/attachment.d.ts.map +1 -1
  37. package/lib/attachment.js +3 -5
  38. package/lib/attachment.js.map +1 -1
  39. package/lib/container.d.ts.map +1 -1
  40. package/lib/container.js +8 -8
  41. package/lib/container.js.map +1 -1
  42. package/lib/containerStorageAdapter.d.ts +2 -2
  43. package/lib/containerStorageAdapter.d.ts.map +1 -1
  44. package/lib/containerStorageAdapter.js +1 -1
  45. package/lib/containerStorageAdapter.js.map +1 -1
  46. package/lib/createAndLoadContainerUtils.js +2 -2
  47. package/lib/createAndLoadContainerUtils.js.map +1 -1
  48. package/lib/frozenServices.d.ts +10 -1
  49. package/lib/frozenServices.d.ts.map +1 -1
  50. package/lib/frozenServices.js +20 -1
  51. package/lib/frozenServices.js.map +1 -1
  52. package/lib/index.d.ts +1 -0
  53. package/lib/index.d.ts.map +1 -1
  54. package/lib/index.js +1 -0
  55. package/lib/index.js.map +1 -1
  56. package/lib/legacyAlpha.d.ts +1 -0
  57. package/lib/packageVersion.d.ts +1 -1
  58. package/lib/packageVersion.js +1 -1
  59. package/lib/packageVersion.js.map +1 -1
  60. package/lib/serializedStateManager.d.ts +19 -12
  61. package/lib/serializedStateManager.d.ts.map +1 -1
  62. package/lib/serializedStateManager.js +114 -100
  63. package/lib/serializedStateManager.js.map +1 -1
  64. package/lib/utils.d.ts +10 -4
  65. package/lib/utils.d.ts.map +1 -1
  66. package/lib/utils.js +41 -24
  67. package/lib/utils.js.map +1 -1
  68. package/package.json +11 -11
  69. package/src/attachment.ts +7 -13
  70. package/src/container.ts +12 -10
  71. package/src/containerStorageAdapter.ts +5 -6
  72. package/src/createAndLoadContainerUtils.ts +2 -2
  73. package/src/frozenServices.ts +28 -2
  74. package/src/index.ts +1 -0
  75. package/src/packageVersion.ts +1 -1
  76. package/src/serializedStateManager.ts +166 -132
  77. package/src/utils.ts +53 -31
@@ -10,6 +10,7 @@ import type {
10
10
  IEvent,
11
11
  ITelemetryBaseLogger,
12
12
  } from "@fluidframework/core-interfaces";
13
+ import type { IDisposable } from "@fluidframework/core-interfaces/internal";
13
14
  import { Timer, assert } from "@fluidframework/core-utils/internal";
14
15
  import {
15
16
  FetchSource,
@@ -21,7 +22,7 @@ import {
21
22
  type IVersion,
22
23
  type ISequencedDocumentMessage,
23
24
  } from "@fluidframework/driver-definitions/internal";
24
- import { getSnapshotTree } from "@fluidframework/driver-utils/internal";
25
+ import { getSnapshotTree, isInstanceOfISnapshot } from "@fluidframework/driver-utils/internal";
25
26
  import {
26
27
  type MonitoringContext,
27
28
  PerformanceEvent,
@@ -30,10 +31,14 @@ import {
30
31
  } from "@fluidframework/telemetry-utils/internal";
31
32
 
32
33
  import {
33
- type ISerializableBlobContents,
34
34
  getBlobContentsFromTree,
35
+ type ISerializableBlobContents,
35
36
  } from "./containerStorageAdapter.js";
36
- import { convertSnapshotToSnapshotInfo, getDocumentAttributes } from "./utils.js";
37
+ import {
38
+ convertISnapshotToSnapshotWithBlobs,
39
+ convertSnapshotToSnapshotInfo,
40
+ getDocumentAttributes,
41
+ } from "./utils.js";
37
42
 
38
43
  /**
39
44
  * This is very similar to {@link @fluidframework/protocol-definitions/internal#ISnapshot}, but the difference is
@@ -75,7 +80,7 @@ export interface IPendingContainerState extends SnapshotWithBlobs {
75
80
  /**
76
81
  * Any group snapshots (aka delay-loaded) we've downloaded from the service for this container
77
82
  */
78
- loadedGroupIdSnapshots?: Record<string, ISnapshotInfo>;
83
+ loadedGroupIdSnapshots?: Record<string, SerializedSnapshotInfo>;
79
84
  /**
80
85
  * All ops since base snapshot sequence number up to the latest op
81
86
  * seen when the container was closed. Used to apply stashed (saved pending)
@@ -117,9 +122,14 @@ export interface IPendingDetachedContainerState extends SnapshotWithBlobs {
117
122
  pendingRuntimeState?: unknown;
118
123
  }
119
124
 
120
- export interface ISnapshotInfo extends SnapshotWithBlobs {
125
+ export interface SerializedSnapshotInfo extends SnapshotWithBlobs {
126
+ snapshotSequenceNumber: number;
127
+ }
128
+
129
+ interface ISnapshotInfo {
121
130
  snapshotSequenceNumber: number;
122
131
  snapshotFetchedTime?: number | undefined;
132
+ snapshot: ISnapshot | ISnapshotTree;
123
133
  }
124
134
 
125
135
  export type ISerializedStateManagerDocumentStorageService = Pick<
@@ -161,10 +171,10 @@ class RefreshPromiseTracker {
161
171
  * as well as the snapshot to be used for serialization.
162
172
  * It also keeps track of container dirty state and which local ops have been processed
163
173
  */
164
- export class SerializedStateManager {
174
+ export class SerializedStateManager implements IDisposable {
165
175
  private readonly processedOps: ISequencedDocumentMessage[] = [];
166
176
  private readonly mc: MonitoringContext;
167
- private snapshot: ISnapshotInfo | undefined;
177
+ private snapshotInfo: ISnapshotInfo | undefined;
168
178
  private latestSnapshot: ISnapshotInfo | undefined;
169
179
  private readonly refreshTracker = new RefreshPromiseTracker(
170
180
  // eslint-disable-next-line unicorn/consistent-function-scoping
@@ -176,12 +186,13 @@ export class SerializedStateManager {
176
186
  error,
177
187
  ),
178
188
  );
179
- private readonly lastSavedOpSequenceNumber: number = 0;
180
- private readonly refreshTimer: Timer;
189
+ private lastSavedOpSequenceNumber: number = 0;
190
+ private readonly refreshTimer: Timer | undefined;
181
191
  private readonly snapshotRefreshTimeoutMs: number = 60 * 60 * 24 * 1000;
192
+ readonly #snapshotRefreshEnabled: boolean;
193
+ #disposed: boolean = false;
182
194
 
183
195
  /**
184
- * @param pendingLocalState - The pendingLocalState being rehydrated, if any (undefined when loading directly from storage)
185
196
  * @param subLogger - Container's logger to use as parent for our logger
186
197
  * @param storageAdapter - Storage adapter for fetching snapshots
187
198
  * @param _offlineLoadEnabled - Is serializing/rehydrating containers allowed?
@@ -189,7 +200,6 @@ export class SerializedStateManager {
189
200
  * @param containerDirty - Is the container "dirty"? That's the opposite of "saved" - there is pending state that may not have been received yet by the service.
190
201
  */
191
202
  constructor(
192
- private readonly pendingLocalState: IPendingContainerState | undefined,
193
203
  subLogger: ITelemetryBaseLogger,
194
204
  private readonly storageAdapter: ISerializedStateManagerDocumentStorageService,
195
205
  private readonly _offlineLoadEnabled: boolean,
@@ -204,19 +214,30 @@ export class SerializedStateManager {
204
214
  });
205
215
 
206
216
  this.snapshotRefreshTimeoutMs = snapshotRefreshTimeoutMs ?? this.snapshotRefreshTimeoutMs;
207
- this.refreshTimer = new Timer(this.snapshotRefreshTimeoutMs, () =>
208
- this.tryRefreshSnapshot(),
209
- );
210
- // special case handle. Obtaining the last saved op seq num to avoid
211
- // refreshing the snapshot before we have processed it. It could cause
212
- // a subsequent stashing to have a newer snapshot than allowed.
213
- if (pendingLocalState && pendingLocalState.savedOps.length > 0) {
214
- const savedOpsSize = pendingLocalState.savedOps.length;
215
- this.lastSavedOpSequenceNumber =
216
- pendingLocalState.savedOps[savedOpsSize - 1].sequenceNumber;
217
- }
217
+
218
+ this.#snapshotRefreshEnabled =
219
+ _offlineLoadEnabled &&
220
+ (this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") ??
221
+ this.mc.config.getBoolean("Fluid.Container.enableOfflineFull")) === true;
222
+
223
+ this.refreshTimer = this.#snapshotRefreshEnabled
224
+ ? new Timer(this.snapshotRefreshTimeoutMs, () => this.tryRefreshSnapshot())
225
+ : undefined;
218
226
  containerEvent.on("saved", () => this.updateSnapshotAndProcessedOpsMaybe());
219
227
  }
228
+ public get disposed(): boolean {
229
+ return this.#disposed;
230
+ }
231
+ dispose(): void {
232
+ this.#disposed = true;
233
+ this.refreshTimer?.clear();
234
+ }
235
+
236
+ private verifyNotDisposed(): void {
237
+ if (this.#disposed) {
238
+ throw new Error("SerializedStateManager used after dispose.");
239
+ }
240
+ }
220
241
 
221
242
  public get offlineLoadEnabled(): boolean {
222
243
  return this._offlineLoadEnabled;
@@ -248,69 +269,74 @@ export class SerializedStateManager {
248
269
  * Otherwise, fetch it from storage (according to specifiedVersion if provided).
249
270
  *
250
271
  * @param specifiedVersion - If a version is specified and we don't have pendingLocalState, fetch this version from storage.
251
- * @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree.
272
+ * @param pendingLocalState - The pendingLocalState being rehydrated, if any (undefined when loading directly from storage)
252
273
  * @returns The snapshot to boot the container from
253
274
  */
254
- public async fetchSnapshot(specifiedVersion: string | undefined): Promise<{
255
- baseSnapshot: ISnapshot | ISnapshotTree;
275
+ public async fetchSnapshot(
276
+ specifiedVersion: string | undefined,
277
+ pendingLocalState: IPendingContainerState | undefined,
278
+ ): Promise<{
279
+ snapshot: ISnapshot | ISnapshotTree;
256
280
  version: IVersion | undefined;
257
281
  attributes: IDocumentAttributes;
258
282
  }> {
259
- if (this.pendingLocalState === undefined) {
260
- const { baseSnapshot, version } = await getSnapshot(
283
+ this.verifyNotDisposed();
284
+ if (pendingLocalState === undefined) {
285
+ const { snapshot, version } = await getSnapshot(
261
286
  this.mc,
262
287
  this.storageAdapter,
263
288
  this.supportGetSnapshotApi(),
264
289
  specifiedVersion,
265
290
  );
266
- const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(baseSnapshot);
291
+ const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(snapshot);
267
292
  const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshotTree);
268
- // non-interactive clients will not have any pending state we want to save
269
293
  if (this.offlineLoadEnabled) {
270
- // we defer getting the blobs to not impact the container load flow
271
- // only getPendingState depends on the resolution of this promise
272
- this.refreshTracker.setPromise(
273
- getBlobContentsFromTree(baseSnapshot, this.storageAdapter).then((snapshotBlobs) => {
274
- this.snapshot = {
275
- baseSnapshot: baseSnapshotTree,
276
- snapshotBlobs,
277
- snapshotSequenceNumber: attributes.sequenceNumber,
278
- };
279
- this.refreshTimer.start();
280
- return attributes.sequenceNumber;
281
- }),
282
- );
294
+ this.refreshTimer?.start();
295
+ this.snapshotInfo = {
296
+ snapshot,
297
+ snapshotSequenceNumber: attributes.sequenceNumber,
298
+ };
283
299
  }
284
- return { baseSnapshot, version, attributes };
300
+ return { snapshot, version, attributes };
285
301
  } else {
286
- const { baseSnapshot, snapshotBlobs } = this.pendingLocalState;
302
+ const { baseSnapshot, snapshotBlobs, savedOps } = pendingLocalState;
287
303
  const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshot);
288
- this.snapshot = {
289
- baseSnapshot,
290
- snapshotBlobs,
291
- snapshotSequenceNumber: attributes.sequenceNumber,
292
- };
293
- this.tryRefreshSnapshot();
294
304
  const blobContents = new Map<string, ArrayBuffer>();
295
305
  for (const [id, value] of Object.entries(snapshotBlobs)) {
296
306
  blobContents.set(id, stringToBuffer(value, "utf8"));
297
307
  }
298
- const iSnapshot: ISnapshot = {
299
- sequenceNumber: this.snapshot.snapshotSequenceNumber,
308
+ const snapshot: ISnapshot = {
309
+ sequenceNumber: attributes.sequenceNumber,
300
310
  snapshotTree: baseSnapshot,
301
311
  blobContents,
302
312
  latestSequenceNumber: undefined,
303
313
  ops: [],
304
314
  snapshotFormatV: 1,
305
315
  };
306
- return { baseSnapshot: iSnapshot, version: undefined, attributes };
316
+
317
+ if (this.offlineLoadEnabled) {
318
+ // special case handle. Obtaining the last saved op seq num to avoid
319
+ // refreshing the snapshot before we have processed it. It could cause
320
+ // a subsequent stashing to have a newer snapshot than allowed.
321
+ if (savedOps.length > 0) {
322
+ const savedOpsSize = savedOps.length;
323
+ this.lastSavedOpSequenceNumber = savedOps[savedOpsSize - 1].sequenceNumber;
324
+ }
325
+
326
+ this.snapshotInfo = {
327
+ snapshot,
328
+ snapshotSequenceNumber: attributes.sequenceNumber,
329
+ };
330
+ this.tryRefreshSnapshot();
331
+ }
332
+ return { snapshot, version: undefined, attributes };
307
333
  }
308
334
  }
309
335
 
310
336
  private tryRefreshSnapshot(): void {
311
337
  if (
312
- (this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") ??
313
- this.mc.config.getBoolean("Fluid.Container.enableOfflineFull")) === true &&
338
+ this.#snapshotRefreshEnabled &&
339
+ !this.#disposed &&
314
340
  !this.refreshTracker.hasPromise &&
315
341
  this.latestSnapshot === undefined
316
342
  ) {
@@ -332,6 +358,10 @@ export class SerializedStateManager {
332
358
  supportGetSnapshotApi,
333
359
  );
334
360
 
361
+ if (this.#disposed) {
362
+ return -1;
363
+ }
364
+
335
365
  // These are loading groupIds that the containerRuntime has requested over its lifetime.
336
366
  // We will fetch the latest snapshot for the groupIds, which will update storageAdapter.loadedGroupIdSnapshots's cache
337
367
  const downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);
@@ -360,6 +390,7 @@ export class SerializedStateManager {
360
390
  private updateSnapshotAndProcessedOpsMaybe(): number {
361
391
  const snapshotSequenceNumber = this.latestSnapshot?.snapshotSequenceNumber;
362
392
  if (
393
+ this.#disposed ||
363
394
  snapshotSequenceNumber === undefined ||
364
395
  this.processedOps.length === 0 ||
365
396
  this.processedOps[this.processedOps.length - 1].sequenceNumber <
@@ -382,17 +413,17 @@ export class SerializedStateManager {
382
413
  snapshotSequenceNumber,
383
414
  firstProcessedOpSequenceNumber,
384
415
  lastProcessedOpSequenceNumber,
385
- stashedSnapshotSequenceNumber: this.snapshot?.snapshotSequenceNumber,
416
+ stashedSnapshotSequenceNumber: this.snapshotInfo?.snapshotSequenceNumber,
386
417
  });
387
418
  this.latestSnapshot = undefined;
388
- this.refreshTimer.restart();
419
+ this.refreshTimer?.restart();
389
420
  } else if (snapshotSequenceNumber <= lastProcessedOpSequenceNumber) {
390
421
  // Snapshot seq num is between the first and last processed op.
391
422
  // Remove the ops that are already part of the snapshot
392
423
  this.processedOps.splice(0, snapshotSequenceNumber - firstProcessedOpSequenceNumber + 1);
393
- this.snapshot = this.latestSnapshot;
424
+ this.snapshotInfo = this.latestSnapshot;
394
425
  this.latestSnapshot = undefined;
395
- this.refreshTimer.restart();
426
+ this.refreshTimer?.restart();
396
427
  this.mc.logger.sendTelemetryEvent({
397
428
  eventName: "SnapshotRefreshed",
398
429
  snapshotSequenceNumber,
@@ -410,31 +441,15 @@ export class SerializedStateManager {
410
441
  * base snapshot when attaching.
411
442
  * @param snapshot - snapshot and blobs collected while attaching (a form of the attach summary)
412
443
  */
413
- public setInitialSnapshot(snapshot: SnapshotWithBlobs | undefined): void {
444
+ public setInitialSnapshot(snapshot: ISnapshot): void {
445
+ this.verifyNotDisposed();
414
446
  if (this.offlineLoadEnabled) {
415
- assert(
416
- this.snapshot === undefined,
417
- 0x937 /* inital snapshot should only be defined once */,
418
- );
419
- assert(snapshot !== undefined, 0x938 /* attachment snapshot should be defined */);
420
- const { baseSnapshot, snapshotBlobs } = snapshot;
421
- const attributesHash =
422
- ".protocol" in baseSnapshot.trees
423
- ? baseSnapshot.trees[".protocol"].blobs.attributes
424
- : baseSnapshot.blobs[".attributes"];
425
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
426
- const attributes = JSON.parse(snapshotBlobs[attributesHash]);
427
- assert(
428
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
429
- attributes.sequenceNumber === 0,
430
- 0x939 /* trying to set a non attachment snapshot */,
431
- );
432
- this.snapshot = {
433
- ...snapshot,
434
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
435
- snapshotSequenceNumber: attributes.sequenceNumber as number,
447
+ this.snapshotInfo = {
448
+ snapshot,
449
+ snapshotSequenceNumber: snapshot.sequenceNumber ?? 0,
450
+ snapshotFetchedTime: Date.now(),
436
451
  };
437
- this.refreshTimer.start();
452
+ this.refreshTimer?.start();
438
453
  }
439
454
  }
440
455
 
@@ -447,6 +462,11 @@ export class SerializedStateManager {
447
462
  runtime: Pick<IRuntime, "getPendingLocalState">,
448
463
  resolvedUrl: IResolvedUrl,
449
464
  ): Promise<string> {
465
+ this.verifyNotDisposed();
466
+ if (!this.offlineLoadEnabled) {
467
+ throw new UsageError("Can't get pending local state unless offline load is enabled");
468
+ }
469
+
450
470
  return PerformanceEvent.timedExecAsync(
451
471
  this.mc.logger,
452
472
  {
@@ -460,25 +480,14 @@ export class SerializedStateManager {
460
480
  clientId,
461
481
  },
462
482
  async () => {
463
- if (!this.offlineLoadEnabled) {
464
- throw new UsageError("Can't get pending local state unless offline load is enabled");
465
- }
466
- if (this.snapshot === undefined && this.refreshTracker.hasPromise) {
467
- // we deferred the initial download of the snapshot to not block
468
- // the container load flow, so if it is not resolved
469
- // and we don't have a snapshot, we will wait for the download
470
- // to finish.
471
- await this.refreshTracker.Promise;
472
- }
473
-
474
- assert(this.snapshot !== undefined, 0x8e5 /* no base data */);
483
+ assert(this.snapshotInfo !== undefined, 0x8e5 /* no base data */);
475
484
  const pendingRuntimeState = await runtime.getPendingLocalState({
476
485
  notifyImminentClosure: false,
477
- snapshotSequenceNumber: this.snapshot.snapshotSequenceNumber,
478
- sessionExpiryTimerStarted: this.snapshot.snapshotFetchedTime,
486
+ snapshotSequenceNumber: this.snapshotInfo.snapshotSequenceNumber,
487
+ sessionExpiryTimerStarted: this.snapshotInfo.snapshotFetchedTime,
479
488
  });
480
489
  // This conversion is required because ArrayBufferLike doesn't survive JSON.stringify
481
- const loadedGroupIdSnapshots = {};
490
+ const loadedGroupIdSnapshots: Record<string, SerializedSnapshotInfo> = {};
482
491
  let hasGroupIdSnapshots = false;
483
492
  const groupIdSnapshots = Object.entries(this.storageAdapter.loadedGroupIdSnapshots);
484
493
  if (groupIdSnapshots.length > 0) {
@@ -487,11 +496,20 @@ export class SerializedStateManager {
487
496
  loadedGroupIdSnapshots[groupId] = convertSnapshotToSnapshotInfo(snapshot);
488
497
  }
489
498
  }
499
+
500
+ const snapshotWithBlobs: SnapshotWithBlobs = isInstanceOfISnapshot(
501
+ this.snapshotInfo.snapshot,
502
+ )
503
+ ? convertISnapshotToSnapshotWithBlobs(this.snapshotInfo.snapshot)
504
+ : await convertSnapshotTreeToSnapshotWithBlobs(
505
+ this.snapshotInfo.snapshot,
506
+ this.storageAdapter,
507
+ );
508
+
490
509
  const pendingState: IPendingContainerState = {
491
510
  attached: true,
492
511
  pendingRuntimeState,
493
- baseSnapshot: this.snapshot.baseSnapshot,
494
- snapshotBlobs: this.snapshot.snapshotBlobs,
512
+ ...snapshotWithBlobs,
495
513
  loadedGroupIdSnapshots: hasGroupIdSnapshots ? loadedGroupIdSnapshots : undefined,
496
514
  savedOps: this.processedOps,
497
515
  url: resolvedUrl.url,
@@ -504,6 +522,17 @@ export class SerializedStateManager {
504
522
  }
505
523
  }
506
524
 
525
+ async function convertSnapshotTreeToSnapshotWithBlobs(
526
+ snapshot: ISnapshotTree,
527
+ storageAdapter: ISerializedStateManagerDocumentStorageService,
528
+ ): Promise<SnapshotWithBlobs> {
529
+ const snapshotBlobs = await getBlobContentsFromTree(snapshot, storageAdapter);
530
+ return {
531
+ baseSnapshot: snapshot,
532
+ snapshotBlobs,
533
+ };
534
+ }
535
+
507
536
  /**
508
537
  * Retrieves the most recent snapshot and returns its info.
509
538
  *
@@ -517,41 +546,46 @@ export async function getLatestSnapshotInfo(
517
546
  storageAdapter: ISerializedStateManagerDocumentStorageService,
518
547
  supportGetSnapshotApi: boolean,
519
548
  ): Promise<ISnapshotInfo | undefined> {
520
- return PerformanceEvent.timedExecAsync(
549
+ return PerformanceEvent.timedExecAsync<ISnapshotInfo | undefined>(
521
550
  mc.logger,
522
551
  { eventName: "GetLatestSnapshotInfo" },
523
- async () => {
524
- // get the latest non cached snapshot version
525
- const specifiedVersion: IVersion[] = await storageAdapter.getVersions(
526
- // eslint-disable-next-line unicorn/no-null
527
- null,
528
- 1,
529
- "getLatestSnapshotInfo",
530
- FetchSource.noCache,
531
- );
532
- const { baseSnapshot } = await getSnapshot(
533
- mc,
534
- storageAdapter,
535
- supportGetSnapshotApi,
536
- specifiedVersion[0]?.id,
537
- );
552
+ async (event) => {
553
+ try {
554
+ // get the latest non cached snapshot version
555
+ const specifiedVersion: IVersion[] = await storageAdapter.getVersions(
556
+ // eslint-disable-next-line unicorn/no-null
557
+ null,
558
+ 1,
559
+ "getLatestSnapshotInfo",
560
+ FetchSource.noCache,
561
+ );
562
+ const { snapshot: baseSnapshot } = await getSnapshot(
563
+ mc,
564
+ storageAdapter,
565
+ supportGetSnapshotApi,
566
+ specifiedVersion[0]?.id,
567
+ );
538
568
 
539
- const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(baseSnapshot);
540
- const snapshotFetchedTime = Date.now();
541
- const snapshotBlobs = await getBlobContentsFromTree(baseSnapshot, storageAdapter);
542
- const attributes: IDocumentAttributes = await getDocumentAttributes(
543
- storageAdapter,
544
- baseSnapshotTree,
545
- );
546
- const snapshotSequenceNumber = attributes.sequenceNumber;
547
- return {
548
- baseSnapshot: baseSnapshotTree,
549
- snapshotBlobs,
550
- snapshotSequenceNumber,
551
- snapshotFetchedTime,
552
- };
569
+ const { sequenceNumber, snapshotTree } = isInstanceOfISnapshot(baseSnapshot)
570
+ ? baseSnapshot
571
+ : { snapshotTree: baseSnapshot, sequenceNumber: undefined };
572
+
573
+ const snapshotSequenceNumber: number =
574
+ sequenceNumber ??
575
+ (await getDocumentAttributes(storageAdapter, snapshotTree).then(
576
+ (a) => a.sequenceNumber,
577
+ ));
578
+ return {
579
+ snapshot: baseSnapshot,
580
+ snapshotSequenceNumber,
581
+ snapshotFetchedTime: Date.now(),
582
+ };
583
+ } catch (error) {
584
+ event.cancel(undefined, error);
585
+ }
586
+ return undefined;
553
587
  },
554
- ).catch(() => undefined);
588
+ );
555
589
  }
556
590
 
557
591
  /**
@@ -571,12 +605,12 @@ async function getSnapshot(
571
605
  >,
572
606
  supportGetSnapshotApi: boolean,
573
607
  specifiedVersion: string | undefined,
574
- ): Promise<{ baseSnapshot: ISnapshot | ISnapshotTree; version?: IVersion }> {
608
+ ): Promise<{ snapshot: ISnapshot | ISnapshotTree; version?: IVersion }> {
575
609
  const { snapshot, version } = supportGetSnapshotApi
576
610
  ? await fetchISnapshot(mc, storageAdapter, specifiedVersion)
577
611
  : await fetchISnapshotTree(mc, storageAdapter, specifiedVersion);
578
612
  assert(snapshot !== undefined, 0x8e4 /* Snapshot should exist */);
579
- return { baseSnapshot: snapshot, version };
613
+ return { snapshot, version };
580
614
  }
581
615
 
582
616
  /**