@fluidframework/container-loader 2.80.0 → 2.81.0-374083

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 (49) hide show
  1. package/dist/connectionManager.d.ts.map +1 -1
  2. package/dist/connectionManager.js +2 -0
  3. package/dist/connectionManager.js.map +1 -1
  4. package/dist/container.js +1 -1
  5. package/dist/container.js.map +1 -1
  6. package/dist/deltaManager.d.ts.map +1 -1
  7. package/dist/deltaManager.js +2 -0
  8. package/dist/deltaManager.js.map +1 -1
  9. package/dist/packageVersion.d.ts +1 -1
  10. package/dist/packageVersion.d.ts.map +1 -1
  11. package/dist/packageVersion.js +1 -1
  12. package/dist/packageVersion.js.map +1 -1
  13. package/dist/serializedStateManager.d.ts +7 -11
  14. package/dist/serializedStateManager.d.ts.map +1 -1
  15. package/dist/serializedStateManager.js +17 -77
  16. package/dist/serializedStateManager.js.map +1 -1
  17. package/dist/snapshotRefresher.d.ts +68 -0
  18. package/dist/snapshotRefresher.d.ts.map +1 -0
  19. package/dist/snapshotRefresher.js +167 -0
  20. package/dist/snapshotRefresher.js.map +1 -0
  21. package/eslint.config.mts +4 -4
  22. package/lib/connectionManager.d.ts.map +1 -1
  23. package/lib/connectionManager.js +2 -0
  24. package/lib/connectionManager.js.map +1 -1
  25. package/lib/container.js +1 -1
  26. package/lib/container.js.map +1 -1
  27. package/lib/deltaManager.d.ts.map +1 -1
  28. package/lib/deltaManager.js +2 -0
  29. package/lib/deltaManager.js.map +1 -1
  30. package/lib/packageVersion.d.ts +1 -1
  31. package/lib/packageVersion.d.ts.map +1 -1
  32. package/lib/packageVersion.js +1 -1
  33. package/lib/packageVersion.js.map +1 -1
  34. package/lib/serializedStateManager.d.ts +7 -11
  35. package/lib/serializedStateManager.d.ts.map +1 -1
  36. package/lib/serializedStateManager.js +18 -78
  37. package/lib/serializedStateManager.js.map +1 -1
  38. package/lib/snapshotRefresher.d.ts +68 -0
  39. package/lib/snapshotRefresher.d.ts.map +1 -0
  40. package/lib/snapshotRefresher.js +163 -0
  41. package/lib/snapshotRefresher.js.map +1 -0
  42. package/package.json +15 -15
  43. package/src/connectionManager.ts +2 -0
  44. package/src/container.ts +1 -1
  45. package/src/deltaManager.ts +2 -0
  46. package/src/packageVersion.ts +1 -1
  47. package/src/serializedStateManager.ts +27 -99
  48. package/src/snapshotRefresher.ts +201 -0
  49. package/.eslintrc.cjs +0 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "2.80.0",
3
+ "version": "2.81.0-374083",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -129,13 +129,13 @@
129
129
  "temp-directory": "nyc/.nyc_output"
130
130
  },
131
131
  "dependencies": {
132
- "@fluid-internal/client-utils": "~2.80.0",
133
- "@fluidframework/container-definitions": "~2.80.0",
134
- "@fluidframework/core-interfaces": "~2.80.0",
135
- "@fluidframework/core-utils": "~2.80.0",
136
- "@fluidframework/driver-definitions": "~2.80.0",
137
- "@fluidframework/driver-utils": "~2.80.0",
138
- "@fluidframework/telemetry-utils": "~2.80.0",
132
+ "@fluid-internal/client-utils": "2.81.0-374083",
133
+ "@fluidframework/container-definitions": "2.81.0-374083",
134
+ "@fluidframework/core-interfaces": "2.81.0-374083",
135
+ "@fluidframework/core-utils": "2.81.0-374083",
136
+ "@fluidframework/driver-definitions": "2.81.0-374083",
137
+ "@fluidframework/driver-utils": "2.81.0-374083",
138
+ "@fluidframework/telemetry-utils": "2.81.0-374083",
139
139
  "@types/events_pkg": "npm:@types/events@^3.0.0",
140
140
  "@ungap/structured-clone": "^1.2.0",
141
141
  "debug": "^4.3.4",
@@ -146,14 +146,14 @@
146
146
  "devDependencies": {
147
147
  "@arethetypeswrong/cli": "^0.18.2",
148
148
  "@biomejs/biome": "~1.9.3",
149
- "@fluid-internal/client-utils": "~2.80.0",
150
- "@fluid-internal/mocha-test-setup": "~2.80.0",
151
- "@fluid-private/test-loader-utils": "~2.80.0",
152
- "@fluid-tools/build-cli": "^0.62.0",
149
+ "@fluid-internal/client-utils": "2.81.0-374083",
150
+ "@fluid-internal/mocha-test-setup": "2.81.0-374083",
151
+ "@fluid-private/test-loader-utils": "2.81.0-374083",
152
+ "@fluid-tools/build-cli": "^0.63.0",
153
153
  "@fluidframework/build-common": "^2.0.3",
154
- "@fluidframework/build-tools": "^0.62.0",
155
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.74.0",
156
- "@fluidframework/eslint-config-fluid": "~2.80.0",
154
+ "@fluidframework/build-tools": "^0.63.0",
155
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.80.0",
156
+ "@fluidframework/eslint-config-fluid": "2.81.0-374083",
157
157
  "@microsoft/api-extractor": "7.52.11",
158
158
  "@types/debug": "^4.1.5",
159
159
  "@types/double-ended-queue": "^2.1.0",
@@ -564,6 +564,7 @@ export class ConnectionManager implements IConnectionManager {
564
564
  this.logger.sendTelemetryEvent(
565
565
  {
566
566
  eventName: "ConnectionReceived",
567
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- using ?. could change behavior
567
568
  connected: connection !== undefined && connection.disposed === false,
568
569
  },
569
570
  undefined,
@@ -573,6 +574,7 @@ export class ConnectionManager implements IConnectionManager {
573
574
  this.logger.sendTelemetryEvent(
574
575
  {
575
576
  eventName: "ConnectToDeltaStreamException",
577
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- using ?. could change behavior
576
578
  connected: connection !== undefined && connection.disposed === false,
577
579
  },
578
580
  undefined,
package/src/container.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- /* eslint-disable unicorn/consistent-function-scoping */
6
+ /* eslint-disable unicorn/consistent-function-scoping, @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/prefer-optional-chain */
7
7
 
8
8
  import {
9
9
  TypedEventEmitter,
@@ -668,6 +668,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
668
668
  throw new Error("Delta manager is not attached");
669
669
  }
670
670
 
671
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy
671
672
  if (this.deltaStorage === undefined) {
672
673
  this.deltaStorage = await docService.connectToDeltaStorage();
673
674
  }
@@ -964,6 +965,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
964
965
  duplicate++;
965
966
  } else if (message.sequenceNumber !== prev + 1) {
966
967
  gap++;
968
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy
967
969
  if (firstMissing === undefined) {
968
970
  firstMissing = prev + 1;
969
971
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.80.0";
9
+ export const pkgVersion = "2.81.0-374083";
@@ -11,7 +11,7 @@ import type {
11
11
  ITelemetryBaseLogger,
12
12
  } from "@fluidframework/core-interfaces";
13
13
  import type { IDisposable } from "@fluidframework/core-interfaces/internal";
14
- import { Timer, assert } from "@fluidframework/core-utils/internal";
14
+ import { assert } from "@fluidframework/core-utils/internal";
15
15
  import {
16
16
  FetchSource,
17
17
  type IDocumentStorageService,
@@ -35,6 +35,7 @@ import {
35
35
  type ContainerStorageAdapter,
36
36
  type ISerializableBlobContents,
37
37
  } from "./containerStorageAdapter.js";
38
+ import { SnapshotRefresher } from "./snapshotRefresher.js";
38
39
  import {
39
40
  convertISnapshotToSnapshotWithBlobs,
40
41
  convertSnapshotToSnapshotInfo,
@@ -127,7 +128,7 @@ export interface SerializedSnapshotInfo extends SnapshotWithBlobs {
127
128
  snapshotSequenceNumber: number;
128
129
  }
129
130
 
130
- interface ISnapshotInfo {
131
+ export interface ISnapshotInfo {
131
132
  snapshotSequenceNumber: number;
132
133
  snapshotFetchedTime?: number | undefined;
133
134
  snapshot: ISnapshot | ISnapshotTree;
@@ -144,27 +145,6 @@ interface ISerializerEvent extends IEvent {
144
145
  (event: "saved", listener: (dirty: boolean) => void): void;
145
146
  }
146
147
 
147
- class RefreshPromiseTracker {
148
- public get hasPromise(): boolean {
149
- return this.#promise !== undefined;
150
- }
151
- public get Promise(): Promise<number> | undefined {
152
- return this.#promise;
153
- }
154
- constructor(private readonly catchHandler: (error: Error) => void) {}
155
-
156
- #promise: Promise<number> | undefined;
157
- setPromise(p: Promise<number>): void {
158
- if (this.hasPromise) {
159
- throw new Error("Cannot set promise while promise exists");
160
- }
161
- this.#promise = p.finally(() => {
162
- this.#promise = undefined;
163
- });
164
- p.catch(this.catchHandler);
165
- }
166
- }
167
-
168
148
  /**
169
149
  * Helper class to manage the state of the container needed for proper serialization.
170
150
  *
@@ -177,20 +157,8 @@ export class SerializedStateManager implements IDisposable {
177
157
  private readonly mc: MonitoringContext;
178
158
  private snapshotInfo: ISnapshotInfo | undefined;
179
159
  private latestSnapshot: ISnapshotInfo | undefined;
180
- private readonly refreshTracker = new RefreshPromiseTracker(
181
- // eslint-disable-next-line unicorn/consistent-function-scoping
182
- (error) =>
183
- this.mc.logger.sendErrorEvent(
184
- {
185
- eventName: "RefreshLatestSnapshotFailed",
186
- },
187
- error,
188
- ),
189
- );
190
160
  private lastSavedOpSequenceNumber: number = 0;
191
- private readonly refreshTimer: Timer | undefined;
192
- private readonly snapshotRefreshTimeoutMs: number = 60 * 60 * 24 * 1000;
193
- readonly #snapshotRefreshEnabled: boolean;
161
+ private readonly snapshotRefresher: SnapshotRefresher | undefined;
194
162
  #disposed: boolean = false;
195
163
 
196
164
  /**
@@ -214,16 +182,17 @@ export class SerializedStateManager implements IDisposable {
214
182
  namespace: "serializedStateManager",
215
183
  });
216
184
 
217
- this.snapshotRefreshTimeoutMs = snapshotRefreshTimeoutMs ?? this.snapshotRefreshTimeoutMs;
218
-
219
- this.#snapshotRefreshEnabled =
220
- this.offlineLoadEnabled &&
221
- (this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") ??
222
- this.mc.config.getBoolean("Fluid.Container.enableOfflineFull")) === true;
223
-
224
- this.refreshTimer = this.#snapshotRefreshEnabled
225
- ? new Timer(this.snapshotRefreshTimeoutMs, () => this.tryRefreshSnapshot())
185
+ this.snapshotRefresher = this.offlineLoadEnabled
186
+ ? new SnapshotRefresher(
187
+ subLogger,
188
+ this.storageAdapter,
189
+ this.offlineLoadEnabled,
190
+ this.supportGetSnapshotApi,
191
+ (snapshot: ISnapshotInfo) => this.handleSnapshotRefreshed(snapshot),
192
+ snapshotRefreshTimeoutMs,
193
+ )
226
194
  : undefined;
195
+
227
196
  containerEvent.on("saved", () => this.updateSnapshotAndProcessedOpsMaybe());
228
197
  }
229
198
  public get disposed(): boolean {
@@ -231,7 +200,7 @@ export class SerializedStateManager implements IDisposable {
231
200
  }
232
201
  dispose(): void {
233
202
  this.#disposed = true;
234
- this.refreshTimer?.clear();
203
+ this.snapshotRefresher?.dispose();
235
204
  }
236
205
 
237
206
  private verifyNotDisposed(): void {
@@ -245,8 +214,8 @@ export class SerializedStateManager implements IDisposable {
245
214
  * only intended to be used for testing purposes.
246
215
  * @returns The snapshot sequence number associated with the latest fetched snapshot
247
216
  */
248
- public get refreshSnapshotP(): Promise<number | undefined> | undefined {
249
- return this.refreshTracker.Promise;
217
+ public get refreshSnapshotP(): Promise<number> | undefined {
218
+ return this.snapshotRefresher?.refreshSnapshotP;
250
219
  }
251
220
 
252
221
  /**
@@ -288,7 +257,7 @@ export class SerializedStateManager implements IDisposable {
288
257
  const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(snapshot);
289
258
  const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshotTree);
290
259
  if (this.offlineLoadEnabled) {
291
- this.refreshTimer?.start();
260
+ this.snapshotRefresher?.startTimer();
292
261
  this.snapshotInfo = {
293
262
  snapshot,
294
263
  snapshotSequenceNumber: attributes.sequenceNumber,
@@ -326,60 +295,19 @@ export class SerializedStateManager implements IDisposable {
326
295
  snapshot,
327
296
  snapshotSequenceNumber: attributes.sequenceNumber,
328
297
  };
329
- this.tryRefreshSnapshot();
298
+ this.snapshotRefresher?.tryRefreshSnapshot();
330
299
  }
331
300
  return { snapshot, version: undefined, attributes };
332
301
  }
333
302
  }
334
303
 
335
- private tryRefreshSnapshot(): void {
336
- if (
337
- this.#snapshotRefreshEnabled &&
338
- !this.#disposed &&
339
- !this.refreshTracker.hasPromise &&
340
- this.latestSnapshot === undefined
341
- ) {
342
- // Don't block on the refresh snapshot call - it is for the next time we serialize, not booting this incarnation
343
- this.refreshTracker.setPromise(this.refreshLatestSnapshot(this.supportGetSnapshotApi()));
344
- }
345
- }
346
-
347
304
  /**
348
- * Fetch the latest snapshot for the container, including delay-loaded groupIds if pendingLocalState was provided and contained any groupIds.
349
- * Note that this will update the StorageAdapter's cached snapshots for the groupIds (if present)
350
- *
351
- * @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree (must be true to fetch by groupIds)
305
+ * Handles the snapshotRefreshed event from SnapshotRefresher.
306
+ * Decides whether to accept the new snapshot based on processed ops.
307
+ * @returns The snapshot sequence number if updated, -1 otherwise
352
308
  */
353
- private async refreshLatestSnapshot(supportGetSnapshotApi: boolean): Promise<number> {
354
- this.latestSnapshot = await getLatestSnapshotInfo(
355
- this.mc,
356
- this.storageAdapter,
357
- supportGetSnapshotApi,
358
- );
359
-
360
- if (this.#disposed) {
361
- return -1;
362
- }
363
-
364
- // These are loading groupIds that the containerRuntime has requested over its lifetime.
365
- // We will fetch the latest snapshot for the groupIds, which will update storageAdapter.loadedGroupIdSnapshots's cache
366
- const downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);
367
- if (supportGetSnapshotApi && downloadedGroupIds.length > 0) {
368
- assert(
369
- this.storageAdapter.getSnapshot !== undefined,
370
- 0x972 /* getSnapshot should exist */,
371
- );
372
- // (This is a separate network call from above because it requires work for storage to add a special base groupId)
373
- const snapshot = await this.storageAdapter.getSnapshot({
374
- versionId: undefined,
375
- scenarioName: "getLatestSnapshotInfo",
376
- cacheSnapshot: false,
377
- loadingGroupIds: downloadedGroupIds,
378
- fetchSource: FetchSource.noCache,
379
- });
380
- assert(snapshot !== undefined, 0x973 /* Snapshot should exist */);
381
- }
382
-
309
+ private handleSnapshotRefreshed(latestSnapshot: ISnapshotInfo): number {
310
+ this.latestSnapshot = latestSnapshot;
383
311
  return this.updateSnapshotAndProcessedOpsMaybe();
384
312
  }
385
313
 
@@ -415,14 +343,14 @@ export class SerializedStateManager implements IDisposable {
415
343
  stashedSnapshotSequenceNumber: this.snapshotInfo?.snapshotSequenceNumber,
416
344
  });
417
345
  this.latestSnapshot = undefined;
418
- this.refreshTimer?.restart();
346
+ this.snapshotRefresher?.clearLatestSnapshot();
419
347
  } else if (snapshotSequenceNumber <= lastProcessedOpSequenceNumber) {
420
348
  // Snapshot seq num is between the first and last processed op.
421
349
  // Remove the ops that are already part of the snapshot
422
350
  this.processedOps.splice(0, snapshotSequenceNumber - firstProcessedOpSequenceNumber + 1);
423
351
  this.snapshotInfo = this.latestSnapshot;
424
352
  this.latestSnapshot = undefined;
425
- this.refreshTimer?.restart();
353
+ this.snapshotRefresher?.clearLatestSnapshot();
426
354
  this.mc.logger.sendTelemetryEvent({
427
355
  eventName: "SnapshotRefreshed",
428
356
  snapshotSequenceNumber,
@@ -448,7 +376,7 @@ export class SerializedStateManager implements IDisposable {
448
376
  snapshotSequenceNumber: snapshot.sequenceNumber ?? 0,
449
377
  snapshotFetchedTime: Date.now(),
450
378
  };
451
- this.refreshTimer?.start();
379
+ this.snapshotRefresher?.startTimer();
452
380
  }
453
381
  }
454
382
 
@@ -0,0 +1,201 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
7
+ import type { IDisposable } from "@fluidframework/core-interfaces/internal";
8
+ import { assert, Timer } from "@fluidframework/core-utils/internal";
9
+ import { FetchSource } from "@fluidframework/driver-definitions/internal";
10
+ import {
11
+ createChildMonitoringContext,
12
+ type MonitoringContext,
13
+ } from "@fluidframework/telemetry-utils/internal";
14
+
15
+ import {
16
+ getLatestSnapshotInfo,
17
+ type ISerializedStateManagerDocumentStorageService,
18
+ type ISnapshotInfo,
19
+ } from "./serializedStateManager.js";
20
+
21
+ class RefreshPromiseTracker {
22
+ public get hasPromise(): boolean {
23
+ return this.#promise !== undefined;
24
+ }
25
+ public get promise(): Promise<number> | undefined {
26
+ return this.#promise;
27
+ }
28
+ constructor(private readonly catchHandler: (error: Error) => void) {}
29
+
30
+ #promise: Promise<number> | undefined;
31
+ setPromise(p: Promise<number>): void {
32
+ if (this.hasPromise) {
33
+ throw new Error(
34
+ "Cannot start new snapshot refresh while a refresh is already in progress",
35
+ );
36
+ }
37
+ this.#promise = p.finally(() => {
38
+ this.#promise = undefined;
39
+ });
40
+ p.catch(this.catchHandler);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Manages periodic refresh of the latest snapshot for a document.
46
+ *
47
+ * `SnapshotRefresher` polls the storage service for the most recent snapshot and, when a newer
48
+ * snapshot is discovered, invokes the provided `onSnapshotRefreshed` callback with the updated
49
+ * snapshot metadata. It is responsible for:
50
+ *
51
+ * - Tracking the most recent snapshot that has been observed.
52
+ * - Scheduling and managing refresh attempts via an internal timer.
53
+ * - Emitting telemetry for successful and failed refresh attempts.
54
+ *
55
+ * The refresh behavior can be configured via constructor arguments, including whether offline
56
+ * loading and the `getSnapshot` API are supported, as well as the refresh timeout. Callers
57
+ * should dispose this instance when snapshot refresh is no longer needed to stop any pending
58
+ * timers and prevent further refresh attempts.
59
+ */
60
+ export class SnapshotRefresher implements IDisposable {
61
+ private readonly mc: MonitoringContext;
62
+ private latestSnapshot: ISnapshotInfo | undefined;
63
+ #disposed: boolean = false;
64
+
65
+ public get disposed(): boolean {
66
+ return this.#disposed;
67
+ }
68
+
69
+ private readonly refreshTracker = new RefreshPromiseTracker(
70
+ // eslint-disable-next-line unicorn/consistent-function-scoping
71
+ (error) =>
72
+ this.mc.logger.sendErrorEvent(
73
+ {
74
+ eventName: "RefreshLatestSnapshotFailed",
75
+ },
76
+ error,
77
+ ),
78
+ );
79
+ private readonly refreshTimer: Timer | undefined;
80
+ private readonly snapshotRefreshTimeoutMs: number = 60 * 60 * 24 * 1000;
81
+ readonly #snapshotRefreshEnabled: boolean;
82
+
83
+ constructor(
84
+ subLogger: ITelemetryBaseLogger,
85
+ private readonly storageAdapter: ISerializedStateManagerDocumentStorageService,
86
+ private readonly offlineLoadEnabled: boolean,
87
+ private readonly supportGetSnapshotApi: () => boolean,
88
+ private readonly onSnapshotRefreshed: (snapshot: ISnapshotInfo) => number,
89
+ snapshotRefreshTimeoutMs?: number,
90
+ ) {
91
+ this.mc = createChildMonitoringContext({
92
+ logger: subLogger,
93
+ namespace: "serializedStateManager",
94
+ });
95
+
96
+ this.snapshotRefreshTimeoutMs = snapshotRefreshTimeoutMs ?? this.snapshotRefreshTimeoutMs;
97
+
98
+ this.#snapshotRefreshEnabled =
99
+ this.offlineLoadEnabled &&
100
+ (this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") ??
101
+ this.mc.config.getBoolean("Fluid.Container.enableOfflineFull")) === true;
102
+
103
+ this.refreshTimer = this.#snapshotRefreshEnabled
104
+ ? new Timer(this.snapshotRefreshTimeoutMs, () => this.tryRefreshSnapshot())
105
+ : undefined;
106
+ }
107
+
108
+ public tryRefreshSnapshot(): void {
109
+ if (
110
+ this.#snapshotRefreshEnabled &&
111
+ !this.#disposed &&
112
+ !this.refreshTracker.hasPromise &&
113
+ this.latestSnapshot === undefined
114
+ ) {
115
+ // Don't block on the refresh snapshot call - it is for the next time we serialize, not booting this incarnation
116
+ this.refreshTracker.setPromise(this.refreshLatestSnapshot(this.supportGetSnapshotApi()));
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Fetch the latest snapshot for the container, including delay-loaded groupIds if pendingLocalState was provided and contained any groupIds.
122
+ * Note that this will update the StorageAdapter's cached snapshots for the groupIds (if present)
123
+ *
124
+ * @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree (must be true to fetch by groupIds)
125
+ */
126
+ private async refreshLatestSnapshot(supportGetSnapshotApi: boolean): Promise<number> {
127
+ this.latestSnapshot = await getLatestSnapshotInfo(
128
+ this.mc,
129
+ this.storageAdapter,
130
+ supportGetSnapshotApi,
131
+ );
132
+
133
+ if (this.#disposed) {
134
+ return -1;
135
+ }
136
+
137
+ // These are loading groupIds that the containerRuntime has requested over its lifetime.
138
+ // We will fetch the latest snapshot for the groupIds, which will update storageAdapter.loadedGroupIdSnapshots's cache
139
+ const downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);
140
+ if (supportGetSnapshotApi && downloadedGroupIds.length > 0) {
141
+ assert(
142
+ this.storageAdapter.getSnapshot !== undefined,
143
+ 0x972 /* getSnapshot should exist */,
144
+ );
145
+ // (This is a separate network call from above because it requires work for storage to add a special base groupId)
146
+ const snapshot = await this.storageAdapter.getSnapshot({
147
+ versionId: undefined,
148
+ scenarioName: "getLatestSnapshotInfo",
149
+ cacheSnapshot: false,
150
+ loadingGroupIds: downloadedGroupIds,
151
+ fetchSource: FetchSource.noCache,
152
+ });
153
+ assert(snapshot !== undefined, 0x973 /* Snapshot should exist */);
154
+ }
155
+
156
+ // Notify the manager about the fetched snapshot - let it decide what to do with it
157
+ const result =
158
+ this.latestSnapshot === undefined ? -1 : this.onSnapshotRefreshed(this.latestSnapshot);
159
+
160
+ this.refreshTimer?.restart();
161
+ return result;
162
+ }
163
+
164
+ /**
165
+ * Clears the latest snapshot after it's been consumed by the manager.
166
+ * This allows the next refresh cycle to proceed.
167
+ */
168
+ public clearLatestSnapshot(): void {
169
+ this.latestSnapshot = undefined;
170
+ }
171
+
172
+ /**
173
+ * Starts the refresh timer.
174
+ */
175
+ public startTimer(): void {
176
+ this.refreshTimer?.start();
177
+ }
178
+
179
+ /**
180
+ * Restarts the refresh timer.
181
+ */
182
+ public restartTimer(): void {
183
+ this.refreshTimer?.restart();
184
+ }
185
+
186
+ /**
187
+ * Gets the current refresh promise for testing purposes.
188
+ * @returns The snapshot sequence number promise, or undefined if no refresh is in progress
189
+ */
190
+ public get refreshSnapshotP(): Promise<number> | undefined {
191
+ return this.refreshTracker.promise;
192
+ }
193
+
194
+ /**
195
+ * Disposes the refresher and clears the timer.
196
+ */
197
+ public dispose(): void {
198
+ this.#disposed = true;
199
+ this.refreshTimer?.clear();
200
+ }
201
+ }
package/.eslintrc.cjs DELETED
@@ -1,24 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
-
6
- module.exports = {
7
- extends: [require.resolve("@fluidframework/eslint-config-fluid"), "prettier"],
8
- parserOptions: {
9
- project: ["./tsconfig.json", "./src/test/tsconfig.json"],
10
- },
11
- rules: {
12
- "@fluid-internal/fluid/no-unchecked-record-access": "warn",
13
- },
14
- overrides: [
15
- {
16
- // Rules only for test files
17
- files: ["*.spec.ts", "src/test/**"],
18
- rules: {
19
- // Test files are run in node only so additional node libraries can be used.
20
- "import-x/no-nodejs-modules": ["error", { allow: ["node:assert"] }],
21
- },
22
- },
23
- ],
24
- };