@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.
Files changed (84) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/container-loader.legacy.alpha.api.md +31 -0
  3. package/dist/container.d.ts.map +1 -1
  4. package/dist/container.js +12 -8
  5. package/dist/container.js.map +1 -1
  6. package/dist/containerStorageAdapter.d.ts +7 -13
  7. package/dist/containerStorageAdapter.d.ts.map +1 -1
  8. package/dist/containerStorageAdapter.js +9 -14
  9. package/dist/containerStorageAdapter.js.map +1 -1
  10. package/dist/createAndLoadContainerUtils.d.ts +13 -0
  11. package/dist/createAndLoadContainerUtils.d.ts.map +1 -1
  12. package/dist/createAndLoadContainerUtils.js +118 -2
  13. package/dist/createAndLoadContainerUtils.js.map +1 -1
  14. package/dist/frozenServices.d.ts +10 -1
  15. package/dist/frozenServices.d.ts.map +1 -1
  16. package/dist/frozenServices.js +24 -4
  17. package/dist/frozenServices.js.map +1 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +4 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/legacyAlpha.d.ts +7 -1
  23. package/dist/packageVersion.d.ts +1 -1
  24. package/dist/packageVersion.d.ts.map +1 -1
  25. package/dist/packageVersion.js +1 -1
  26. package/dist/packageVersion.js.map +1 -1
  27. package/dist/serializedStateManager.d.ts +10 -6
  28. package/dist/serializedStateManager.d.ts.map +1 -1
  29. package/dist/serializedStateManager.js +76 -77
  30. package/dist/serializedStateManager.js.map +1 -1
  31. package/dist/summarizerResultTypes.d.ts +96 -0
  32. package/dist/summarizerResultTypes.d.ts.map +1 -0
  33. package/dist/summarizerResultTypes.js +9 -0
  34. package/dist/summarizerResultTypes.js.map +1 -0
  35. package/dist/utils.d.ts +4 -5
  36. package/dist/utils.d.ts.map +1 -1
  37. package/dist/utils.js +14 -13
  38. package/dist/utils.js.map +1 -1
  39. package/lib/container.d.ts.map +1 -1
  40. package/lib/container.js +13 -9
  41. package/lib/container.js.map +1 -1
  42. package/lib/containerStorageAdapter.d.ts +7 -13
  43. package/lib/containerStorageAdapter.d.ts.map +1 -1
  44. package/lib/containerStorageAdapter.js +10 -15
  45. package/lib/containerStorageAdapter.js.map +1 -1
  46. package/lib/createAndLoadContainerUtils.d.ts +13 -0
  47. package/lib/createAndLoadContainerUtils.d.ts.map +1 -1
  48. package/lib/createAndLoadContainerUtils.js +117 -2
  49. package/lib/createAndLoadContainerUtils.js.map +1 -1
  50. package/lib/frozenServices.d.ts +10 -1
  51. package/lib/frozenServices.d.ts.map +1 -1
  52. package/lib/frozenServices.js +20 -1
  53. package/lib/frozenServices.js.map +1 -1
  54. package/lib/index.d.ts +3 -1
  55. package/lib/index.d.ts.map +1 -1
  56. package/lib/index.js +2 -1
  57. package/lib/index.js.map +1 -1
  58. package/lib/legacyAlpha.d.ts +7 -1
  59. package/lib/packageVersion.d.ts +1 -1
  60. package/lib/packageVersion.d.ts.map +1 -1
  61. package/lib/packageVersion.js +1 -1
  62. package/lib/packageVersion.js.map +1 -1
  63. package/lib/serializedStateManager.d.ts +10 -6
  64. package/lib/serializedStateManager.d.ts.map +1 -1
  65. package/lib/serializedStateManager.js +78 -79
  66. package/lib/serializedStateManager.js.map +1 -1
  67. package/lib/summarizerResultTypes.d.ts +96 -0
  68. package/lib/summarizerResultTypes.d.ts.map +1 -0
  69. package/lib/summarizerResultTypes.js +6 -0
  70. package/lib/summarizerResultTypes.js.map +1 -0
  71. package/lib/utils.d.ts +4 -5
  72. package/lib/utils.d.ts.map +1 -1
  73. package/lib/utils.js +15 -14
  74. package/lib/utils.js.map +1 -1
  75. package/package.json +11 -11
  76. package/src/container.ts +19 -14
  77. package/src/containerStorageAdapter.ts +14 -18
  78. package/src/createAndLoadContainerUtils.ts +176 -2
  79. package/src/frozenServices.ts +28 -2
  80. package/src/index.ts +8 -0
  81. package/src/packageVersion.ts +1 -1
  82. package/src/serializedStateManager.ts +118 -109
  83. package/src/summarizerResultTypes.ts +115 -0
  84. 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, ISnapshotInfo>;
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 ISnapshotInfo extends SnapshotWithBlobs {
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
- IDocumentStorageService,
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 snapshot: ISnapshotInfo | undefined;
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
- baseSnapshot: ISnapshot | ISnapshotTree;
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 { baseSnapshot, version } = await getSnapshot(
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(baseSnapshot);
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
- // we defer getting the blobs to not impact the container load flow
290
- // only getPendingState depends on the resolution of this promise
291
- this.refreshTracker.setPromise(
292
- getBlobContentsFromTree(baseSnapshot, this.storageAdapter).then((snapshotBlobs) => {
293
- this.snapshot = {
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 { baseSnapshot, version, attributes };
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
- const iSnapshot: ISnapshot = {
327
- sequenceNumber: this.snapshot.snapshotSequenceNumber,
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
- return { baseSnapshot: iSnapshot, version: undefined, attributes };
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.snapshot?.snapshotSequenceNumber,
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.snapshot = this.latestSnapshot;
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
- assert(
450
- this.snapshot === undefined,
451
- 0x937 /* inital snapshot should only be defined once */,
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
- if (!this.offlineLoadEnabled) {
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.snapshot.snapshotSequenceNumber,
506
- sessionExpiryTimerStarted: this.snapshot.snapshotFetchedTime,
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
- baseSnapshot: this.snapshot.baseSnapshot,
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
- // get the latest non cached snapshot version
553
- const specifiedVersion: IVersion[] = await storageAdapter.getVersions(
554
- // eslint-disable-next-line unicorn/no-null
555
- null,
556
- 1,
557
- "getLatestSnapshotInfo",
558
- FetchSource.noCache,
559
- );
560
- const { baseSnapshot } = await getSnapshot(
561
- mc,
562
- storageAdapter,
563
- supportGetSnapshotApi,
564
- specifiedVersion[0]?.id,
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
- const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(baseSnapshot);
568
- const snapshotFetchedTime = Date.now();
569
- const snapshotBlobs = await getBlobContentsFromTree(baseSnapshot, storageAdapter);
570
- const attributes: IDocumentAttributes = await getDocumentAttributes(
571
- storageAdapter,
572
- baseSnapshotTree,
573
- );
574
- const snapshotSequenceNumber = attributes.sequenceNumber;
575
- return {
576
- baseSnapshot: baseSnapshotTree,
577
- snapshotBlobs,
578
- snapshotSequenceNumber,
579
- snapshotFetchedTime,
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
- ).catch(() => undefined);
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<{ baseSnapshot: ISnapshot | ISnapshotTree; version?: IVersion }> {
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 { baseSnapshot: snapshot, version };
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
- ISnapshotInfo,
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): ISnapshotInfo {
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: ISnapshotInfo,
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
- baseSnapshot: ISnapshotTree,
280
- snapshotBlobs: ISerializableBlobContents,
281
- ): ISnapshotTreeWithBlobContents => {
282
- const blobsContents: { [path: string]: ArrayBufferLike } = {};
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(baseSnapshot.blobs)) {
286
- if (snapshotBlobs[id] !== undefined) {
287
- blobsContents[id] = stringToBuffer(snapshotBlobs[id], "utf8");
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(baseSnapshot.trees)) {
294
- trees[path] = combineSnapshotTreeAndSnapshotBlobs(tree, snapshotBlobs);
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 snapshotTreeWithBlobContents: ISnapshotTreeWithBlobContents = {
299
- ...baseSnapshot,
300
- blobsContents,
298
+ const snapshot: ISnapshotTreeWithBlobContents = {
299
+ ...snapshotTree,
300
+ blobsContents: currentTreeBlobs,
301
301
  trees,
302
302
  };
303
303
 
304
- return snapshotTreeWithBlobContents;
304
+ return snapshot;
305
305
  };
306
306
 
307
307
  export function isDeltaStreamConnectionForbiddenError(