@fluidframework/container-loader 2.0.0-dev-rc.3.0.0.254866 → 2.0.0-dev-rc.4.0.0.261659

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 (151) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/api-report/container-loader.api.md +7 -3
  3. package/dist/audience.d.ts +6 -4
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js +18 -3
  6. package/dist/audience.js.map +1 -1
  7. package/dist/connectionManager.d.ts +6 -2
  8. package/dist/connectionManager.d.ts.map +1 -1
  9. package/dist/connectionManager.js +40 -16
  10. package/dist/connectionManager.js.map +1 -1
  11. package/dist/connectionStateHandler.d.ts +29 -8
  12. package/dist/connectionStateHandler.d.ts.map +1 -1
  13. package/dist/connectionStateHandler.js +49 -36
  14. package/dist/connectionStateHandler.js.map +1 -1
  15. package/dist/container.d.ts +6 -10
  16. package/dist/container.d.ts.map +1 -1
  17. package/dist/container.js +126 -113
  18. package/dist/container.js.map +1 -1
  19. package/dist/containerContext.d.ts +1 -1
  20. package/dist/containerContext.d.ts.map +1 -1
  21. package/dist/containerContext.js.map +1 -1
  22. package/dist/containerStorageAdapter.d.ts +12 -3
  23. package/dist/containerStorageAdapter.d.ts.map +1 -1
  24. package/dist/containerStorageAdapter.js +42 -4
  25. package/dist/containerStorageAdapter.js.map +1 -1
  26. package/dist/debugLogger.d.ts +1 -2
  27. package/dist/debugLogger.d.ts.map +1 -1
  28. package/dist/debugLogger.js.map +1 -1
  29. package/dist/deltaManager.d.ts +3 -4
  30. package/dist/deltaManager.d.ts.map +1 -1
  31. package/dist/deltaManager.js +8 -3
  32. package/dist/deltaManager.js.map +1 -1
  33. package/dist/error.d.ts +1 -2
  34. package/dist/error.d.ts.map +1 -1
  35. package/dist/error.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +3 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/{alpha.d.ts → legacy.d.ts} +5 -2
  41. package/dist/loadPaused.d.ts +35 -0
  42. package/dist/loadPaused.d.ts.map +1 -0
  43. package/dist/loadPaused.js +115 -0
  44. package/dist/loadPaused.js.map +1 -0
  45. package/dist/loader.d.ts +1 -1
  46. package/dist/loader.d.ts.map +1 -1
  47. package/dist/loader.js +0 -13
  48. package/dist/loader.js.map +1 -1
  49. package/dist/packageVersion.d.ts +1 -1
  50. package/dist/packageVersion.js +1 -1
  51. package/dist/packageVersion.js.map +1 -1
  52. package/dist/protocol.d.ts.map +1 -1
  53. package/dist/protocol.js +3 -0
  54. package/dist/protocol.js.map +1 -1
  55. package/dist/public.d.ts +2 -1
  56. package/dist/retriableDocumentStorageService.d.ts +1 -1
  57. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  58. package/dist/retriableDocumentStorageService.js.map +1 -1
  59. package/dist/serializedStateManager.d.ts +23 -5
  60. package/dist/serializedStateManager.d.ts.map +1 -1
  61. package/dist/serializedStateManager.js +72 -22
  62. package/dist/serializedStateManager.js.map +1 -1
  63. package/dist/utils.d.ts +2 -2
  64. package/dist/utils.d.ts.map +1 -1
  65. package/dist/utils.js +2 -3
  66. package/dist/utils.js.map +1 -1
  67. package/{dist/beta.d.ts → internal.d.ts} +2 -4
  68. package/{lib/beta.d.ts → legacy.d.ts} +2 -4
  69. package/lib/audience.d.ts +6 -4
  70. package/lib/audience.d.ts.map +1 -1
  71. package/lib/audience.js +19 -4
  72. package/lib/audience.js.map +1 -1
  73. package/lib/connectionManager.d.ts +6 -2
  74. package/lib/connectionManager.d.ts.map +1 -1
  75. package/lib/connectionManager.js +41 -17
  76. package/lib/connectionManager.js.map +1 -1
  77. package/lib/connectionStateHandler.d.ts +29 -8
  78. package/lib/connectionStateHandler.d.ts.map +1 -1
  79. package/lib/connectionStateHandler.js +49 -36
  80. package/lib/connectionStateHandler.js.map +1 -1
  81. package/lib/container.d.ts +6 -10
  82. package/lib/container.d.ts.map +1 -1
  83. package/lib/container.js +126 -113
  84. package/lib/container.js.map +1 -1
  85. package/lib/containerContext.d.ts +1 -1
  86. package/lib/containerContext.d.ts.map +1 -1
  87. package/lib/containerContext.js.map +1 -1
  88. package/lib/containerStorageAdapter.d.ts +12 -3
  89. package/lib/containerStorageAdapter.d.ts.map +1 -1
  90. package/lib/containerStorageAdapter.js +42 -4
  91. package/lib/containerStorageAdapter.js.map +1 -1
  92. package/lib/debugLogger.d.ts +1 -2
  93. package/lib/debugLogger.d.ts.map +1 -1
  94. package/lib/debugLogger.js.map +1 -1
  95. package/lib/deltaManager.d.ts +3 -4
  96. package/lib/deltaManager.d.ts.map +1 -1
  97. package/lib/deltaManager.js +9 -4
  98. package/lib/deltaManager.js.map +1 -1
  99. package/lib/error.d.ts +1 -2
  100. package/lib/error.d.ts.map +1 -1
  101. package/lib/error.js.map +1 -1
  102. package/lib/index.d.ts +1 -0
  103. package/lib/index.d.ts.map +1 -1
  104. package/lib/index.js +1 -0
  105. package/lib/index.js.map +1 -1
  106. package/lib/{alpha.d.ts → legacy.d.ts} +5 -2
  107. package/lib/loadPaused.d.ts +35 -0
  108. package/lib/loadPaused.d.ts.map +1 -0
  109. package/lib/loadPaused.js +111 -0
  110. package/lib/loadPaused.js.map +1 -0
  111. package/lib/loader.d.ts +1 -1
  112. package/lib/loader.d.ts.map +1 -1
  113. package/lib/loader.js +1 -14
  114. package/lib/loader.js.map +1 -1
  115. package/lib/packageVersion.d.ts +1 -1
  116. package/lib/packageVersion.js +1 -1
  117. package/lib/packageVersion.js.map +1 -1
  118. package/lib/protocol.d.ts.map +1 -1
  119. package/lib/protocol.js +3 -0
  120. package/lib/protocol.js.map +1 -1
  121. package/lib/public.d.ts +2 -1
  122. package/lib/retriableDocumentStorageService.d.ts +1 -1
  123. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  124. package/lib/retriableDocumentStorageService.js +1 -1
  125. package/lib/retriableDocumentStorageService.js.map +1 -1
  126. package/lib/serializedStateManager.d.ts +23 -5
  127. package/lib/serializedStateManager.d.ts.map +1 -1
  128. package/lib/serializedStateManager.js +66 -16
  129. package/lib/serializedStateManager.js.map +1 -1
  130. package/lib/utils.d.ts +2 -2
  131. package/lib/utils.d.ts.map +1 -1
  132. package/lib/utils.js +2 -3
  133. package/lib/utils.js.map +1 -1
  134. package/package.json +29 -27
  135. package/src/audience.ts +30 -9
  136. package/src/connectionManager.ts +50 -21
  137. package/src/connectionStateHandler.ts +76 -43
  138. package/src/container.ts +150 -153
  139. package/src/containerContext.ts +1 -1
  140. package/src/containerStorageAdapter.ts +59 -7
  141. package/src/debugLogger.ts +1 -1
  142. package/src/deltaManager.ts +13 -6
  143. package/src/error.ts +1 -1
  144. package/src/index.ts +1 -0
  145. package/src/loadPaused.ts +140 -0
  146. package/src/loader.ts +1 -21
  147. package/src/packageVersion.ts +1 -1
  148. package/src/protocol.ts +4 -0
  149. package/src/retriableDocumentStorageService.ts +5 -2
  150. package/src/serializedStateManager.ts +107 -31
  151. package/src/utils.ts +3 -4
@@ -9,6 +9,7 @@ import {
9
9
  } from "@fluidframework/container-definitions/internal";
10
10
  import { assert } from "@fluidframework/core-utils/internal";
11
11
  import {
12
+ FetchSource,
12
13
  IDocumentStorageService,
13
14
  IResolvedUrl,
14
15
  ISnapshot,
@@ -20,16 +21,16 @@ import {
20
21
  ISnapshotTree,
21
22
  IVersion,
22
23
  } from "@fluidframework/protocol-definitions";
23
- import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
24
24
  import {
25
25
  MonitoringContext,
26
26
  PerformanceEvent,
27
27
  UsageError,
28
28
  createChildMonitoringContext,
29
29
  } from "@fluidframework/telemetry-utils/internal";
30
-
30
+ import type { IEventProvider, IEvent } from "@fluidframework/core-interfaces";
31
+ import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
31
32
  import { ISerializableBlobContents, getBlobContentsFromTree } from "./containerStorageAdapter.js";
32
- import { getDocumentAttributes } from "./utils.js";
33
+ import { convertSnapshotToSnapshotInfo, getDocumentAttributes } from "./utils.js";
33
34
 
34
35
  export interface SnapshotWithBlobs {
35
36
  /**
@@ -45,11 +46,17 @@ export interface SnapshotWithBlobs {
45
46
  /**
46
47
  * State saved by a container at close time, to be used to load a new instance
47
48
  * of the container to the same state
49
+ *
50
+ * This is very similar to {@link @fluidframework/protocol-definitions/internal#ISnapshot}, but the difference is
51
+ * that the blobs of ISnapshot of are ArrayBufferLike, while the blobs of this interface are serializable because
52
+ * they are already converted to string.
53
+ *
48
54
  * @internal
49
55
  */
50
56
  export interface IPendingContainerState extends SnapshotWithBlobs {
51
57
  attached: true;
52
58
  pendingRuntimeState: unknown;
59
+ loadedGroupIdSnapshots?: Record<string, ISnapshotInfo>;
53
60
  /**
54
61
  * All ops since base snapshot sequence number up to the latest op
55
62
  * seen when the container was closed. Used to apply stashed (saved pending)
@@ -73,35 +80,57 @@ export interface IPendingDetachedContainerState extends SnapshotWithBlobs {
73
80
 
74
81
  export interface ISnapshotInfo extends SnapshotWithBlobs {
75
82
  snapshotSequenceNumber: number;
83
+ snapshotFetchedTime?: number | undefined;
84
+ }
85
+
86
+ export type ISerializedStateManagerDocumentStorageService = Pick<
87
+ IDocumentStorageService,
88
+ "getSnapshot" | "getSnapshotTree" | "getVersions" | "readBlob"
89
+ > & {
90
+ loadedGroupIdSnapshots: Record<string, ISnapshot>;
91
+ };
92
+
93
+ interface ISerializerEvent extends IEvent {
94
+ (event: "saved", listener: (dirty: boolean) => void): void;
76
95
  }
77
96
 
78
97
  export class SerializedStateManager {
79
98
  private readonly processedOps: ISequencedDocumentMessage[] = [];
80
- private snapshot: ISnapshotInfo | undefined;
81
99
  private readonly mc: MonitoringContext;
100
+ private snapshot: ISnapshotInfo | undefined;
82
101
  private latestSnapshot: ISnapshotInfo | undefined;
83
102
  private refreshSnapshot: Promise<void> | undefined;
103
+ private readonly lastSavedOpSequenceNumber: number = 0;
84
104
 
85
105
  constructor(
86
106
  private readonly pendingLocalState: IPendingContainerState | undefined,
87
- subLogger: ITelemetryLoggerExt,
88
- private readonly storageAdapter: Pick<
89
- IDocumentStorageService,
90
- "readBlob" | "getSnapshotTree" | "getSnapshot" | "getVersions"
91
- >,
107
+ subLogger: ITelemetryBaseLogger,
108
+ private readonly storageAdapter: ISerializedStateManagerDocumentStorageService,
92
109
  private readonly _offlineLoadEnabled: boolean,
93
- private readonly newSnapshotFetched?: () => void,
110
+ containerEvent: IEventProvider<ISerializerEvent>,
111
+ private readonly containerDirty: () => boolean,
94
112
  ) {
95
113
  this.mc = createChildMonitoringContext({
96
114
  logger: subLogger,
97
115
  namespace: "serializedStateManager",
98
116
  });
117
+
118
+ if (pendingLocalState && pendingLocalState.savedOps.length > 0) {
119
+ const savedOpsSize = pendingLocalState.savedOps.length;
120
+ this.lastSavedOpSequenceNumber =
121
+ pendingLocalState.savedOps[savedOpsSize - 1].sequenceNumber;
122
+ }
123
+ containerEvent.once("saved", () => this.updateSnapshotAndProcessedOpsMaybe());
99
124
  }
100
125
 
101
126
  public get offlineLoadEnabled(): boolean {
102
127
  return this._offlineLoadEnabled;
103
128
  }
104
129
 
130
+ public get waitForInitialRefresh(): Promise<void> | undefined {
131
+ return this.refreshSnapshot;
132
+ }
133
+
105
134
  public addProcessedOp(message: ISequencedDocumentMessage) {
106
135
  if (this.offlineLoadEnabled) {
107
136
  this.processedOps.push(message);
@@ -142,25 +171,52 @@ export class SerializedStateManager {
142
171
  snapshotBlobs,
143
172
  snapshotSequenceNumber: attributes.sequenceNumber,
144
173
  };
145
- this.refreshSnapshot ??= (async () => {
146
- this.latestSnapshot = await getLatestSnapshotInfo(
147
- this.mc,
148
- this.storageAdapter,
149
- supportGetSnapshotApi,
150
- );
151
- this.newSnapshotFetched?.();
152
- this.updateSnapshotAndProcessedOpsMaybe();
153
- })();
174
+
175
+ if (this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") === true)
176
+ this.refreshSnapshot ??= (async () => {
177
+ await this.refreshLatestSnapshot(supportGetSnapshotApi);
178
+ })();
154
179
 
155
180
  return { baseSnapshot, version: undefined };
156
181
  }
157
182
  }
158
183
 
184
+ public async refreshLatestSnapshot(supportGetSnapshotApi: boolean) {
185
+ this.latestSnapshot = await getLatestSnapshotInfo(
186
+ this.mc,
187
+ this.storageAdapter,
188
+ supportGetSnapshotApi,
189
+ );
190
+
191
+ // These are loading groupIds that the containerRuntime has requested over its lifetime.
192
+ const downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);
193
+ // We are making two network calls because it requires work for storage to add a special base groupId.
194
+ if (supportGetSnapshotApi && downloadedGroupIds.length > 0) {
195
+ assert(this.storageAdapter.getSnapshot !== undefined, "getSnapshot should exist");
196
+ const snapshot = await this.storageAdapter.getSnapshot({
197
+ versionId: undefined,
198
+ scenarioName: "getLatestSnapshotInfo",
199
+ cacheSnapshot: false,
200
+ loadingGroupIds: downloadedGroupIds,
201
+ fetchSource: FetchSource.noCache,
202
+ });
203
+ assert(snapshot !== undefined, "Snapshot should exist");
204
+ return convertSnapshotToSnapshotInfo(snapshot);
205
+ }
206
+ this.updateSnapshotAndProcessedOpsMaybe();
207
+ }
208
+
159
209
  /**
160
210
  * Updates class snapshot and processedOps if we have a new snapshot and it's among processedOps range.
161
211
  */
162
212
  private updateSnapshotAndProcessedOpsMaybe() {
163
- if (this.latestSnapshot === undefined || this.processedOps.length === 0) {
213
+ if (
214
+ this.latestSnapshot === undefined ||
215
+ this.processedOps.length === 0 ||
216
+ this.processedOps[this.processedOps.length - 1].sequenceNumber <
217
+ this.lastSavedOpSequenceNumber ||
218
+ this.containerDirty()
219
+ ) {
164
220
  // can't refresh latest snapshot until we have processed the ops up to it.
165
221
  // Pending state would be behind the latest snapshot.
166
222
  return;
@@ -209,15 +265,21 @@ export class SerializedStateManager {
209
265
  */
210
266
  public setInitialSnapshot(snapshot: SnapshotWithBlobs | undefined) {
211
267
  if (this.offlineLoadEnabled) {
212
- assert(this.snapshot === undefined, "inital snapshot should only be defined once");
213
- assert(snapshot !== undefined, "attachment snapshot should be defined");
268
+ assert(
269
+ this.snapshot === undefined,
270
+ 0x937 /* inital snapshot should only be defined once */,
271
+ );
272
+ assert(snapshot !== undefined, 0x938 /* attachment snapshot should be defined */);
214
273
  const { baseSnapshot, snapshotBlobs } = snapshot;
215
274
  const attributesHash =
216
275
  ".protocol" in baseSnapshot.trees
217
276
  ? baseSnapshot.trees[".protocol"].blobs.attributes
218
277
  : baseSnapshot.blobs[".attributes"];
219
278
  const attributes = JSON.parse(snapshotBlobs[attributesHash]);
220
- assert(attributes.sequenceNumber === 0, "trying to set a non attachment snapshot");
279
+ assert(
280
+ attributes.sequenceNumber === 0,
281
+ 0x939 /* trying to set a non attachment snapshot */,
282
+ );
221
283
  this.snapshot = { ...snapshot, snapshotSequenceNumber: attributes.sequenceNumber };
222
284
  }
223
285
  }
@@ -243,16 +305,32 @@ export class SerializedStateManager {
243
305
  );
244
306
  }
245
307
  assert(this.snapshot !== undefined, 0x8e5 /* no base data */);
246
- const pendingRuntimeState = await runtime.getPendingLocalState(props);
308
+ const pendingRuntimeState = await runtime.getPendingLocalState({
309
+ ...props,
310
+ snapshotSequenceNumber: this.snapshot.snapshotSequenceNumber,
311
+ sessionExpiryTimerStarted: this.snapshot.snapshotFetchedTime,
312
+ });
313
+ // This conversion is required because ArrayBufferLike doesn't survive JSON.stringify
314
+ const loadedGroupIdSnapshots = {};
315
+ let hasGroupIdSnapshots = false;
316
+ const groupIdSnapshots = Object.entries(this.storageAdapter.loadedGroupIdSnapshots);
317
+ if (groupIdSnapshots.length > 0) {
318
+ for (const [groupId, snapshot] of groupIdSnapshots) {
319
+ hasGroupIdSnapshots = true;
320
+ loadedGroupIdSnapshots[groupId] = convertSnapshotToSnapshotInfo(snapshot);
321
+ }
322
+ }
247
323
  const pendingState: IPendingContainerState = {
248
324
  attached: true,
249
325
  pendingRuntimeState,
250
326
  baseSnapshot: this.snapshot.baseSnapshot,
251
327
  snapshotBlobs: this.snapshot.snapshotBlobs,
328
+ loadedGroupIdSnapshots: hasGroupIdSnapshots
329
+ ? loadedGroupIdSnapshots
330
+ : undefined,
252
331
  savedOps: this.processedOps,
253
332
  url: resolvedUrl.url,
254
- // no need to save this if there is no pending runtime state
255
- clientId: pendingRuntimeState !== undefined ? clientId : undefined,
333
+ clientId,
256
334
  };
257
335
 
258
336
  return JSON.stringify(pendingState);
@@ -271,10 +349,7 @@ export class SerializedStateManager {
271
349
  */
272
350
  export async function getLatestSnapshotInfo(
273
351
  mc: MonitoringContext,
274
- storageAdapter: Pick<
275
- IDocumentStorageService,
276
- "getSnapshot" | "getSnapshotTree" | "getVersions" | "readBlob"
277
- >,
352
+ storageAdapter: ISerializedStateManagerDocumentStorageService,
278
353
  supportGetSnapshotApi: boolean,
279
354
  ): Promise<ISnapshotInfo | undefined> {
280
355
  return PerformanceEvent.timedExecAsync(
@@ -287,13 +362,14 @@ export async function getLatestSnapshotInfo(
287
362
  supportGetSnapshotApi,
288
363
  undefined,
289
364
  );
365
+ const snapshotFetchedTime = Date.now();
290
366
  const snapshotBlobs = await getBlobContentsFromTree(baseSnapshot, storageAdapter);
291
367
  const attributes: IDocumentAttributes = await getDocumentAttributes(
292
368
  storageAdapter,
293
369
  baseSnapshot,
294
370
  );
295
371
  const snapshotSequenceNumber = attributes.sequenceNumber;
296
- return { baseSnapshot, snapshotBlobs, snapshotSequenceNumber };
372
+ return { baseSnapshot, snapshotBlobs, snapshotSequenceNumber, snapshotFetchedTime };
297
373
  },
298
374
  ).catch(() => undefined);
299
375
  }
package/src/utils.ts CHANGED
@@ -43,7 +43,7 @@ export interface ISnapshotTreeWithBlobContents extends ISnapshotTree {
43
43
  * Interface to represent the parsed parts of IResolvedUrl.url to help
44
44
  * in getting info about different parts of the url.
45
45
  * May not be compatible or relevant for any Url Resolver
46
- * @internal
46
+ * @alpha
47
47
  */
48
48
  export interface IParsedUrl {
49
49
  /**
@@ -72,7 +72,7 @@ export interface IParsedUrl {
72
72
  * with urls of type: protocol://<string>/.../..?<querystring>
73
73
  * @param url - This is the IResolvedUrl.url part of the resolved url.
74
74
  * @returns The IParsedUrl representing the input URL, or undefined if the format was not supported
75
- * @internal
75
+ * @alpha
76
76
  */
77
77
  export function tryParseCompatibleResolvedUrl(url: string): IParsedUrl | undefined {
78
78
  const parsed = new URL(url);
@@ -163,7 +163,6 @@ function convertSummaryToSnapshotAndBlobs(summary: ISummaryTree): SnapshotWithBl
163
163
  throw new LoggingError(
164
164
  "No handles should be there in summary in detached container!!",
165
165
  );
166
- break;
167
166
  default: {
168
167
  unreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);
169
168
  }
@@ -181,7 +180,7 @@ function convertSummaryToSnapshotAndBlobs(summary: ISummaryTree): SnapshotWithBl
181
180
  * @param snapshot - ISnapshot
182
181
  */
183
182
  export function convertSnapshotToSnapshotInfo(snapshot: ISnapshot): ISnapshotInfo {
184
- assert(snapshot.sequenceNumber !== undefined, "Snapshot sequence number is missing");
183
+ assert(snapshot.sequenceNumber !== undefined, 0x93a /* Snapshot sequence number is missing */);
185
184
  const snapshotBlobs: ISerializableBlobContents = {};
186
185
  for (const [blobId, arrayBufferLike] of snapshot.blobContents.entries()) {
187
186
  snapshotBlobs[blobId] = bufferToString(arrayBufferLike, "utf8");