@fluidframework/container-loader 2.74.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 (63) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/connectionManager.d.ts.map +1 -1
  3. package/dist/connectionManager.js +2 -0
  4. package/dist/connectionManager.js.map +1 -1
  5. package/dist/container.d.ts.map +1 -1
  6. package/dist/container.js +3 -3
  7. package/dist/container.js.map +1 -1
  8. package/dist/deltaManager.d.ts +10 -0
  9. package/dist/deltaManager.d.ts.map +1 -1
  10. package/dist/deltaManager.js +44 -1
  11. package/dist/deltaManager.js.map +1 -1
  12. package/dist/loaderLayerCompatState.d.ts +4 -4
  13. package/dist/loaderLayerCompatState.d.ts.map +1 -1
  14. package/dist/loaderLayerCompatState.js +6 -5
  15. package/dist/loaderLayerCompatState.js.map +1 -1
  16. package/dist/packageVersion.d.ts +1 -1
  17. package/dist/packageVersion.d.ts.map +1 -1
  18. package/dist/packageVersion.js +1 -1
  19. package/dist/packageVersion.js.map +1 -1
  20. package/dist/serializedStateManager.d.ts +10 -15
  21. package/dist/serializedStateManager.d.ts.map +1 -1
  22. package/dist/serializedStateManager.js +20 -83
  23. package/dist/serializedStateManager.js.map +1 -1
  24. package/dist/snapshotRefresher.d.ts +68 -0
  25. package/dist/snapshotRefresher.d.ts.map +1 -0
  26. package/dist/snapshotRefresher.js +167 -0
  27. package/dist/snapshotRefresher.js.map +1 -0
  28. package/eslint.config.mts +4 -4
  29. package/lib/connectionManager.d.ts.map +1 -1
  30. package/lib/connectionManager.js +2 -0
  31. package/lib/connectionManager.js.map +1 -1
  32. package/lib/container.d.ts.map +1 -1
  33. package/lib/container.js +3 -3
  34. package/lib/container.js.map +1 -1
  35. package/lib/deltaManager.d.ts +10 -0
  36. package/lib/deltaManager.d.ts.map +1 -1
  37. package/lib/deltaManager.js +44 -1
  38. package/lib/deltaManager.js.map +1 -1
  39. package/lib/loaderLayerCompatState.d.ts +4 -4
  40. package/lib/loaderLayerCompatState.d.ts.map +1 -1
  41. package/lib/loaderLayerCompatState.js +6 -5
  42. package/lib/loaderLayerCompatState.js.map +1 -1
  43. package/lib/packageVersion.d.ts +1 -1
  44. package/lib/packageVersion.d.ts.map +1 -1
  45. package/lib/packageVersion.js +1 -1
  46. package/lib/packageVersion.js.map +1 -1
  47. package/lib/serializedStateManager.d.ts +10 -15
  48. package/lib/serializedStateManager.d.ts.map +1 -1
  49. package/lib/serializedStateManager.js +21 -84
  50. package/lib/serializedStateManager.js.map +1 -1
  51. package/lib/snapshotRefresher.d.ts +68 -0
  52. package/lib/snapshotRefresher.d.ts.map +1 -0
  53. package/lib/snapshotRefresher.js +163 -0
  54. package/lib/snapshotRefresher.js.map +1 -0
  55. package/package.json +20 -20
  56. package/src/connectionManager.ts +2 -0
  57. package/src/container.ts +3 -6
  58. package/src/deltaManager.ts +54 -1
  59. package/src/loaderLayerCompatState.ts +10 -9
  60. package/src/packageVersion.ts +1 -1
  61. package/src/serializedStateManager.ts +29 -105
  62. package/src/snapshotRefresher.ts +201 -0
  63. package/.eslintrc.cjs +0 -24
@@ -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,33 +157,21 @@ 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
  /**
197
165
  * @param subLogger - Container's logger to use as parent for our logger
198
166
  * @param storageAdapter - Storage adapter for fetching snapshots
199
- * @param _offlineLoadEnabled - Is serializing/rehydrating containers allowed?
167
+ * @param offlineLoadEnabled - Is serializing/rehydrating containers allowed?
200
168
  * @param containerEvent - Source of the "saved" event when the container has all its pending state uploaded
201
169
  * @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.
202
170
  */
203
171
  constructor(
204
172
  subLogger: ITelemetryBaseLogger,
205
173
  private readonly storageAdapter: ISerializedStateManagerDocumentStorageService,
206
- private readonly _offlineLoadEnabled: boolean,
174
+ private readonly offlineLoadEnabled: boolean,
207
175
  containerEvent: IEventProvider<ISerializerEvent>,
208
176
  private readonly containerDirty: () => boolean,
209
177
  private readonly supportGetSnapshotApi: () => boolean,
@@ -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
- _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 {
@@ -240,17 +209,13 @@ export class SerializedStateManager implements IDisposable {
240
209
  }
241
210
  }
242
211
 
243
- public get offlineLoadEnabled(): boolean {
244
- return this._offlineLoadEnabled;
245
- }
246
-
247
212
  /**
248
213
  * Promise that will resolve (or reject) once we've tried to download the latest snapshot(s) from storage
249
214
  * only intended to be used for testing purposes.
250
215
  * @returns The snapshot sequence number associated with the latest fetched snapshot
251
216
  */
252
- public get refreshSnapshotP(): Promise<number | undefined> | undefined {
253
- return this.refreshTracker.Promise;
217
+ public get refreshSnapshotP(): Promise<number> | undefined {
218
+ return this.snapshotRefresher?.refreshSnapshotP;
254
219
  }
255
220
 
256
221
  /**
@@ -292,7 +257,7 @@ export class SerializedStateManager implements IDisposable {
292
257
  const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(snapshot);
293
258
  const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshotTree);
294
259
  if (this.offlineLoadEnabled) {
295
- this.refreshTimer?.start();
260
+ this.snapshotRefresher?.startTimer();
296
261
  this.snapshotInfo = {
297
262
  snapshot,
298
263
  snapshotSequenceNumber: attributes.sequenceNumber,
@@ -330,60 +295,19 @@ export class SerializedStateManager implements IDisposable {
330
295
  snapshot,
331
296
  snapshotSequenceNumber: attributes.sequenceNumber,
332
297
  };
333
- this.tryRefreshSnapshot();
298
+ this.snapshotRefresher?.tryRefreshSnapshot();
334
299
  }
335
300
  return { snapshot, version: undefined, attributes };
336
301
  }
337
302
  }
338
303
 
339
- private tryRefreshSnapshot(): void {
340
- if (
341
- this.#snapshotRefreshEnabled &&
342
- !this.#disposed &&
343
- !this.refreshTracker.hasPromise &&
344
- this.latestSnapshot === undefined
345
- ) {
346
- // Don't block on the refresh snapshot call - it is for the next time we serialize, not booting this incarnation
347
- this.refreshTracker.setPromise(this.refreshLatestSnapshot(this.supportGetSnapshotApi()));
348
- }
349
- }
350
-
351
304
  /**
352
- * Fetch the latest snapshot for the container, including delay-loaded groupIds if pendingLocalState was provided and contained any groupIds.
353
- * Note that this will update the StorageAdapter's cached snapshots for the groupIds (if present)
354
- *
355
- * @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
356
308
  */
357
- private async refreshLatestSnapshot(supportGetSnapshotApi: boolean): Promise<number> {
358
- this.latestSnapshot = await getLatestSnapshotInfo(
359
- this.mc,
360
- this.storageAdapter,
361
- supportGetSnapshotApi,
362
- );
363
-
364
- if (this.#disposed) {
365
- return -1;
366
- }
367
-
368
- // These are loading groupIds that the containerRuntime has requested over its lifetime.
369
- // We will fetch the latest snapshot for the groupIds, which will update storageAdapter.loadedGroupIdSnapshots's cache
370
- const downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);
371
- if (supportGetSnapshotApi && downloadedGroupIds.length > 0) {
372
- assert(
373
- this.storageAdapter.getSnapshot !== undefined,
374
- 0x972 /* getSnapshot should exist */,
375
- );
376
- // (This is a separate network call from above because it requires work for storage to add a special base groupId)
377
- const snapshot = await this.storageAdapter.getSnapshot({
378
- versionId: undefined,
379
- scenarioName: "getLatestSnapshotInfo",
380
- cacheSnapshot: false,
381
- loadingGroupIds: downloadedGroupIds,
382
- fetchSource: FetchSource.noCache,
383
- });
384
- assert(snapshot !== undefined, 0x973 /* Snapshot should exist */);
385
- }
386
-
309
+ private handleSnapshotRefreshed(latestSnapshot: ISnapshotInfo): number {
310
+ this.latestSnapshot = latestSnapshot;
387
311
  return this.updateSnapshotAndProcessedOpsMaybe();
388
312
  }
389
313
 
@@ -419,14 +343,14 @@ export class SerializedStateManager implements IDisposable {
419
343
  stashedSnapshotSequenceNumber: this.snapshotInfo?.snapshotSequenceNumber,
420
344
  });
421
345
  this.latestSnapshot = undefined;
422
- this.refreshTimer?.restart();
346
+ this.snapshotRefresher?.clearLatestSnapshot();
423
347
  } else if (snapshotSequenceNumber <= lastProcessedOpSequenceNumber) {
424
348
  // Snapshot seq num is between the first and last processed op.
425
349
  // Remove the ops that are already part of the snapshot
426
350
  this.processedOps.splice(0, snapshotSequenceNumber - firstProcessedOpSequenceNumber + 1);
427
351
  this.snapshotInfo = this.latestSnapshot;
428
352
  this.latestSnapshot = undefined;
429
- this.refreshTimer?.restart();
353
+ this.snapshotRefresher?.clearLatestSnapshot();
430
354
  this.mc.logger.sendTelemetryEvent({
431
355
  eventName: "SnapshotRefreshed",
432
356
  snapshotSequenceNumber,
@@ -452,7 +376,7 @@ export class SerializedStateManager implements IDisposable {
452
376
  snapshotSequenceNumber: snapshot.sequenceNumber ?? 0,
453
377
  snapshotFetchedTime: Date.now(),
454
378
  };
455
- this.refreshTimer?.start();
379
+ this.snapshotRefresher?.startTimer();
456
380
  }
457
381
  }
458
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
- };