@fluidframework/datastore 2.41.0-338401 → 2.42.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 (76) hide show
  1. package/.eslintrc.cjs +1 -4
  2. package/CHANGELOG.md +8 -0
  3. package/dist/channelContext.d.ts +7 -5
  4. package/dist/channelContext.d.ts.map +1 -1
  5. package/dist/channelContext.js +3 -1
  6. package/dist/channelContext.js.map +1 -1
  7. package/dist/channelDeltaConnection.d.ts +5 -5
  8. package/dist/channelDeltaConnection.d.ts.map +1 -1
  9. package/dist/channelDeltaConnection.js +7 -6
  10. package/dist/channelDeltaConnection.js.map +1 -1
  11. package/dist/channelStorageService.js +2 -2
  12. package/dist/channelStorageService.js.map +1 -1
  13. package/dist/dataStoreRuntime.d.ts +20 -0
  14. package/dist/dataStoreRuntime.d.ts.map +1 -1
  15. package/dist/dataStoreRuntime.js +125 -48
  16. package/dist/dataStoreRuntime.js.map +1 -1
  17. package/dist/fluidHandle.d.ts.map +1 -1
  18. package/dist/fluidHandle.js +6 -2
  19. package/dist/fluidHandle.js.map +1 -1
  20. package/dist/localChannelContext.d.ts +8 -6
  21. package/dist/localChannelContext.d.ts.map +1 -1
  22. package/dist/localChannelContext.js +8 -6
  23. package/dist/localChannelContext.js.map +1 -1
  24. package/dist/localChannelStorageService.d.ts.map +1 -1
  25. package/dist/localChannelStorageService.js +6 -4
  26. package/dist/localChannelStorageService.js.map +1 -1
  27. package/dist/packageVersion.d.ts +1 -1
  28. package/dist/packageVersion.d.ts.map +1 -1
  29. package/dist/packageVersion.js +1 -1
  30. package/dist/packageVersion.js.map +1 -1
  31. package/dist/remoteChannelContext.d.ts +7 -5
  32. package/dist/remoteChannelContext.d.ts.map +1 -1
  33. package/dist/remoteChannelContext.js +5 -4
  34. package/dist/remoteChannelContext.js.map +1 -1
  35. package/lib/channelContext.d.ts +7 -5
  36. package/lib/channelContext.d.ts.map +1 -1
  37. package/lib/channelContext.js +3 -1
  38. package/lib/channelContext.js.map +1 -1
  39. package/lib/channelDeltaConnection.d.ts +5 -5
  40. package/lib/channelDeltaConnection.d.ts.map +1 -1
  41. package/lib/channelDeltaConnection.js +7 -6
  42. package/lib/channelDeltaConnection.js.map +1 -1
  43. package/lib/channelStorageService.js +2 -2
  44. package/lib/channelStorageService.js.map +1 -1
  45. package/lib/dataStoreRuntime.d.ts +20 -0
  46. package/lib/dataStoreRuntime.d.ts.map +1 -1
  47. package/lib/dataStoreRuntime.js +125 -48
  48. package/lib/dataStoreRuntime.js.map +1 -1
  49. package/lib/fluidHandle.d.ts.map +1 -1
  50. package/lib/fluidHandle.js +6 -2
  51. package/lib/fluidHandle.js.map +1 -1
  52. package/lib/localChannelContext.d.ts +8 -6
  53. package/lib/localChannelContext.d.ts.map +1 -1
  54. package/lib/localChannelContext.js +8 -6
  55. package/lib/localChannelContext.js.map +1 -1
  56. package/lib/localChannelStorageService.d.ts.map +1 -1
  57. package/lib/localChannelStorageService.js +6 -4
  58. package/lib/localChannelStorageService.js.map +1 -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/remoteChannelContext.d.ts +7 -5
  64. package/lib/remoteChannelContext.d.ts.map +1 -1
  65. package/lib/remoteChannelContext.js +5 -4
  66. package/lib/remoteChannelContext.js.map +1 -1
  67. package/package.json +16 -16
  68. package/src/channelContext.ts +7 -5
  69. package/src/channelDeltaConnection.ts +19 -19
  70. package/src/channelStorageService.ts +3 -3
  71. package/src/dataStoreRuntime.ts +174 -75
  72. package/src/fluidHandle.ts +7 -3
  73. package/src/localChannelContext.ts +18 -16
  74. package/src/localChannelStorageService.ts +6 -4
  75. package/src/packageVersion.ts +1 -1
  76. package/src/remoteChannelContext.ts +19 -19
@@ -27,6 +27,8 @@ import {
27
27
  IFluidDataStoreRuntime,
28
28
  IFluidDataStoreRuntimeEvents,
29
29
  type IDeltaManagerErased,
30
+ // eslint-disable-next-line import/no-deprecated
31
+ type IFluidDataStoreRuntimeExperimental,
30
32
  } from "@fluidframework/datastore-definitions/internal";
31
33
  import {
32
34
  IClientDetails,
@@ -148,6 +150,22 @@ const defaultPolicies: IFluidDataStorePolicies = {
148
150
  readonlyInStagingMode: true,
149
151
  };
150
152
 
153
+ /**
154
+ * Set up the boxed pendingOpCount value.
155
+ */
156
+ function initializePendingOpCount(): { value: number } {
157
+ let value = 0;
158
+ return {
159
+ get value() {
160
+ return value;
161
+ },
162
+ set value(newValue: number) {
163
+ assert(newValue >= 0, 0xbbd /* pendingOpCount must be non-negative */);
164
+ value = newValue;
165
+ },
166
+ };
167
+ }
168
+
151
169
  /**
152
170
  * Base data store class
153
171
  * @legacy
@@ -201,22 +219,25 @@ export class FluidDataStoreRuntime
201
219
  return this.dataStoreContext.idCompressor;
202
220
  }
203
221
 
204
- public get IFluidHandleContext() {
222
+ // TODO: the methods below should have more specific return typing, per the interfaces they are implementing.
223
+ // Doing so would be a breaking change.
224
+
225
+ public get IFluidHandleContext(): this {
205
226
  return this;
206
227
  }
207
228
 
208
- public get rootRoutingContext() {
229
+ public get rootRoutingContext(): this {
209
230
  return this;
210
231
  }
211
- public get channelsRoutingContext() {
232
+ public get channelsRoutingContext(): this {
212
233
  return this;
213
234
  }
214
- public get objectsRoutingContext() {
235
+ public get objectsRoutingContext(): this {
215
236
  return this;
216
237
  }
217
238
 
218
239
  private _disposed = false;
219
- public get disposed() {
240
+ public get disposed(): boolean {
220
241
  return this._disposed;
221
242
  }
222
243
 
@@ -234,6 +255,8 @@ export class FluidDataStoreRuntime
234
255
 
235
256
  public readonly id: string;
236
257
 
258
+ // TODO: use something other than `any` here (breaking change)
259
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
237
260
  public readonly options: Record<string | number, any>;
238
261
  public readonly deltaManagerInternal: IDeltaManager<
239
262
  ISequencedDocumentMessage,
@@ -337,10 +360,10 @@ export class FluidDataStoreRuntime
337
360
 
338
361
  // Must always receive the data store type inside of the attributes
339
362
  if (tree?.trees !== undefined) {
340
- Object.entries(tree.trees).forEach(([path, subtree]) => {
363
+ for (const [path, subtree] of Object.entries(tree.trees)) {
341
364
  // Issue #4414
342
365
  if (path === "_search") {
343
- return;
366
+ continue;
344
367
  }
345
368
 
346
369
  let channelContext: RemoteChannelContext | RehydratedLocalChannelContext;
@@ -353,10 +376,10 @@ export class FluidDataStoreRuntime
353
376
  // data store, if the data store is loaded after the container is attached, then we missed making
354
377
  // the channel visible. So do it now. Otherwise, add it to local channel context queue, so
355
378
  // that it can be make it visible later with the data store.
356
- if (dataStoreContext.attachState !== AttachState.Detached) {
357
- channelContext.makeVisible();
358
- } else {
379
+ if (dataStoreContext.attachState === AttachState.Detached) {
359
380
  this.localChannelContextQueue.set(path, channelContext);
381
+ } else {
382
+ channelContext.makeVisible();
360
383
  }
361
384
  } else {
362
385
  channelContext = new RemoteChannelContext(
@@ -376,7 +399,7 @@ export class FluidDataStoreRuntime
376
399
  }
377
400
 
378
401
  this.contexts.set(path, channelContext);
379
- });
402
+ }
380
403
  }
381
404
 
382
405
  this.entryPoint = new FluidObjectHandle<FluidObject>(
@@ -416,17 +439,30 @@ export class FluidDataStoreRuntime
416
439
  this.localChangesTelemetryCount =
417
440
  this.mc.config.getNumber("Fluid.Telemetry.LocalChangesTelemetryCount") ?? 10;
418
441
 
419
- // eslint-disable-next-line import/no-deprecated
420
- const base: IContainerRuntimeBaseExperimental | undefined =
442
+ // Reference these properties to avoid unused private member errors.
443
+ // They're accessed via IFluidDataStoreRuntimeExperimental interface.
444
+ // eslint-disable-next-line no-void
445
+ void [this.inStagingMode, this.isDirty];
446
+ }
447
+
448
+ /**
449
+ * Implementation of IFluidDataStoreRuntimeExperimental.inStagingMode
450
+ */
451
+ // eslint-disable-next-line import/no-deprecated
452
+ private get inStagingMode(): IFluidDataStoreRuntimeExperimental["inStagingMode"] {
453
+ return (
421
454
  // eslint-disable-next-line import/no-deprecated
422
- this.dataStoreContext.containerRuntime satisfies IContainerRuntimeBaseExperimental;
423
- if (base !== undefined && "inStagingMode" in base) {
424
- Object.defineProperty(this, "inStagingMode", {
425
- get: () => {
426
- return base.inStagingMode;
427
- },
428
- });
429
- }
455
+ (this.dataStoreContext.containerRuntime as IContainerRuntimeBaseExperimental)
456
+ ?.inStagingMode
457
+ );
458
+ }
459
+
460
+ /**
461
+ * Implementation of IFluidDataStoreRuntimeExperimental.isDirty
462
+ */
463
+ // eslint-disable-next-line import/no-deprecated
464
+ private get isDirty(): IFluidDataStoreRuntimeExperimental["isDirty"] {
465
+ return this.pendingOpCount.value > 0;
430
466
  }
431
467
 
432
468
  get deltaManager(): IDeltaManagerErased {
@@ -508,7 +544,7 @@ export class FluidDataStoreRuntime
508
544
  * IDs cannot start with "_" as it could result in collision of IDs with auto-assigned (by FF) short IDs.
509
545
  * @param id - channel ID.
510
546
  */
511
- protected validateChannelId(id: string) {
547
+ protected validateChannelId(id: string): void {
512
548
  if (id.includes("/")) {
513
549
  throw new UsageError(`Id cannot contain slashes: ${id}`);
514
550
  }
@@ -546,10 +582,7 @@ export class FluidDataStoreRuntime
546
582
  public createChannel(idArg: string | undefined, type: string): IChannel {
547
583
  let id: string;
548
584
 
549
- if (idArg !== undefined) {
550
- id = idArg;
551
- this.validateChannelId(id);
552
- } else {
585
+ if (idArg === undefined) {
553
586
  /**
554
587
  * Return uuid if short-ids are explicitly disabled via feature flags.
555
588
  */
@@ -562,18 +595,21 @@ export class FluidDataStoreRuntime
562
595
  // - uuids
563
596
  // In first two cases we will encode result as strings in more compact form, with leading underscore,
564
597
  // to ensure no overlap with user-provided DDS names (see validateChannelId())
565
- if (this.visibilityState !== VisibilityState.GloballyVisible) {
566
- // container is detached, only one client observes content, no way to hit collisions with other clients.
567
- id = encodeCompactIdToString(2 * this.contexts.size, "_");
568
- } else {
598
+ if (this.visibilityState === VisibilityState.GloballyVisible) {
569
599
  // Due to back-compat, we could not depend yet on generateDocumentUniqueId() being there.
570
600
  // We can remove the need to leverage uuid() as fall-back in couple releases.
571
601
  const res =
572
602
  this.dataStoreContext.containerRuntime.generateDocumentUniqueId?.() ?? uuid();
573
603
  id = typeof res === "number" ? encodeCompactIdToString(2 * res + 1, "_") : res;
604
+ } else {
605
+ // container is detached, only one client observes content, no way to hit collisions with other clients.
606
+ id = encodeCompactIdToString(2 * this.contexts.size, "_");
574
607
  }
575
608
  }
576
609
  assert(!id.includes("/"), 0x8fc /* slash */);
610
+ } else {
611
+ id = idArg;
612
+ this.validateChannelId(id);
577
613
  }
578
614
 
579
615
  this.verifyNotClosed();
@@ -592,7 +628,7 @@ export class FluidDataStoreRuntime
592
628
  return channel;
593
629
  }
594
630
 
595
- private createChannelContext(channel: IChannel) {
631
+ private createChannelContext(channel: IChannel): void {
596
632
  this.notBoundedChannelContextSet.add(channel.id);
597
633
  const context = new LocalChannelContext(
598
634
  channel,
@@ -610,7 +646,7 @@ export class FluidDataStoreRuntime
610
646
  id: string,
611
647
  tree: ISnapshotTree,
612
648
  flatBlobs?: Map<string, ArrayBufferLike>,
613
- ) {
649
+ ): RehydratedLocalChannelContext {
614
650
  return new RehydratedLocalChannelContext(
615
651
  id,
616
652
  this.sharedObjectRegistry,
@@ -672,15 +708,15 @@ export class FluidDataStoreRuntime
672
708
  * visible, it will mark us globally visible. Otherwise, it will mark us globally visible when it becomes
673
709
  * globally visible.
674
710
  */
675
- public makeVisibleAndAttachGraph() {
711
+ public makeVisibleAndAttachGraph(): void {
676
712
  if (this.visibilityState !== VisibilityState.NotVisible) {
677
713
  return;
678
714
  }
679
715
  this.visibilityState = VisibilityState.LocallyVisible;
680
716
 
681
- this.pendingHandlesToMakeVisible.forEach((handle) => {
717
+ for (const handle of this.pendingHandlesToMakeVisible) {
682
718
  handle.attachGraph();
683
- });
719
+ }
684
720
  this.pendingHandlesToMakeVisible.clear();
685
721
  this.dataStoreContext.makeLocallyVisible();
686
722
  }
@@ -688,7 +724,7 @@ export class FluidDataStoreRuntime
688
724
  /**
689
725
  * This function is called when a handle to this data store is added to a visible DDS.
690
726
  */
691
- public attachGraph() {
727
+ public attachGraph(): void {
692
728
  this.makeVisibleAndAttachGraph();
693
729
  }
694
730
 
@@ -701,7 +737,7 @@ export class FluidDataStoreRuntime
701
737
  this.pendingHandlesToMakeVisible.add(toFluidHandleInternal(handle));
702
738
  }
703
739
 
704
- public setConnectionState(connected: boolean, clientId?: string) {
740
+ public setConnectionState(connected: boolean, clientId?: string): void {
705
741
  this.verifyNotClosed();
706
742
 
707
743
  for (const [, object] of this.contexts) {
@@ -745,7 +781,7 @@ export class FluidDataStoreRuntime
745
781
  private createRemoteChannelContext(
746
782
  attachMessage: IAttachMessage,
747
783
  summarizerNodeParams: CreateChildSummarizerNodeParam,
748
- ) {
784
+ ): RemoteChannelContext {
749
785
  const flatBlobs = new Map<string, ArrayBufferLike>();
750
786
  const snapshotTree = buildSnapshotTree(attachMessage.snapshot.entries, flatBlobs);
751
787
 
@@ -773,7 +809,7 @@ export class FluidDataStoreRuntime
773
809
  * store.
774
810
  * @param messageCollection - The collection of messages to process.
775
811
  */
776
- private processChannelMessages(messageCollection: IRuntimeMessageCollection) {
812
+ private processChannelMessages(messageCollection: IRuntimeMessageCollection): void {
777
813
  this.verifyNotClosed();
778
814
 
779
815
  /*
@@ -783,9 +819,9 @@ export class FluidDataStoreRuntime
783
819
  */
784
820
  let currentAddress: string | undefined;
785
821
  let currentMessagesContent: IRuntimeMessagesContent[] = [];
786
- const { messagesContent, local } = messageCollection;
822
+ const { messagesContent, local, envelope } = messageCollection;
787
823
 
788
- const sendBunchedMessages = () => {
824
+ const sendBunchedMessages = (): void => {
789
825
  // Current address will be undefined for the first message in the list.
790
826
  if (currentAddress === undefined) {
791
827
  return;
@@ -796,7 +832,7 @@ export class FluidDataStoreRuntime
796
832
  assert(!!channelContext, 0xa6b /* Channel context not found */);
797
833
 
798
834
  channelContext.processMessages({
799
- envelope: messageCollection.envelope,
835
+ envelope,
800
836
  messagesContent: currentMessagesContent,
801
837
  local,
802
838
  });
@@ -823,7 +859,7 @@ export class FluidDataStoreRuntime
823
859
  sendBunchedMessages();
824
860
  }
825
861
 
826
- private processAttachMessages(messageCollection: IRuntimeMessageCollection) {
862
+ private processAttachMessages(messageCollection: IRuntimeMessageCollection): void {
827
863
  const { envelope, messagesContent, local } = messageCollection;
828
864
  for (const { contents } of messagesContent) {
829
865
  const attachMessage = contents as IAttachMessage;
@@ -868,15 +904,22 @@ export class FluidDataStoreRuntime
868
904
  public processMessages(messageCollection: IRuntimeMessageCollection): void {
869
905
  this.verifyNotClosed();
870
906
 
871
- const { envelope, messagesContent } = messageCollection;
907
+ const { envelope, local, messagesContent } = messageCollection;
908
+
909
+ if (local) {
910
+ this.pendingOpCount.value -= messagesContent.length;
911
+ }
912
+
872
913
  try {
873
914
  switch (envelope.type) {
874
- case DataStoreMessageType.ChannelOp:
915
+ case DataStoreMessageType.ChannelOp: {
875
916
  this.processChannelMessages(messageCollection);
876
917
  break;
877
- case DataStoreMessageType.Attach:
918
+ }
919
+ case DataStoreMessageType.Attach: {
878
920
  this.processAttachMessages(messageCollection);
879
921
  break;
922
+ }
880
923
  default:
881
924
  }
882
925
  } catch (error) {
@@ -892,7 +935,7 @@ export class FluidDataStoreRuntime
892
935
  }
893
936
  }
894
937
 
895
- public processSignal(message: IInboundSignalMessage, local: boolean) {
938
+ public processSignal(message: IInboundSignalMessage, local: boolean): void {
896
939
  this.emit("signal", message, local);
897
940
  }
898
941
 
@@ -929,7 +972,7 @@ export class FluidDataStoreRuntime
929
972
  * - Adds a node for this channel.
930
973
  * @param builder - The builder that contains the GC nodes for this channel's children.
931
974
  */
932
- private updateGCNodes(builder: GCDataBuilder) {
975
+ private updateGCNodes(builder: GCDataBuilder): void {
933
976
  // Add a back route to self in each child's GC nodes. If any child is referenced, then its parent should
934
977
  // be considered referenced as well.
935
978
  builder.addRouteToAllNodes(this.absolutePath);
@@ -993,7 +1036,7 @@ export class FluidDataStoreRuntime
993
1036
  * update their used routes.
994
1037
  * @param usedRoutes - The routes that are used in all contexts in this channel.
995
1038
  */
996
- public updateUsedRoutes(usedRoutes: string[]) {
1039
+ public updateUsedRoutes(usedRoutes: string[]): void {
997
1040
  // Get a map of channel ids to routes used in it.
998
1041
  const usedContextRoutes = unpackChildNodesUsedRoutes(usedRoutes);
999
1042
 
@@ -1132,7 +1175,13 @@ export class FluidDataStoreRuntime
1132
1175
  }
1133
1176
  }
1134
1177
 
1135
- public submitMessage(type: DataStoreMessageType, content: any, localOpMetadata: unknown) {
1178
+ public submitMessage(
1179
+ type: DataStoreMessageType,
1180
+ // TODO: use something other than `any` here (breaking change)
1181
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
1182
+ content: any,
1183
+ localOpMetadata: unknown,
1184
+ ): void {
1136
1185
  this.submit(type, content, localOpMetadata);
1137
1186
  }
1138
1187
 
@@ -1142,9 +1191,9 @@ export class FluidDataStoreRuntime
1142
1191
  * @param content - Content of the signal. Should be a JSON serializable object or primitive.
1143
1192
  * @param targetClientId - When specified, the signal is only sent to the provided client id.
1144
1193
  */
1145
- public submitSignal(type: string, content: unknown, targetClientId?: string) {
1194
+ public submitSignal(type: string, content: unknown, targetClientId?: string): void {
1146
1195
  this.verifyNotClosed();
1147
- return this.dataStoreContext.submitSignal(type, content, targetClientId);
1196
+ this.dataStoreContext.submitSignal(type, content, targetClientId);
1148
1197
  }
1149
1198
 
1150
1199
  /**
@@ -1201,18 +1250,25 @@ export class FluidDataStoreRuntime
1201
1250
  context.makeVisible();
1202
1251
  }
1203
1252
 
1204
- private submitChannelOp(address: string, contents: any, localOpMetadata: unknown) {
1253
+ private submitChannelOp(address: string, contents: unknown, localOpMetadata: unknown): void {
1205
1254
  const envelope: IEnvelope = { address, contents };
1206
1255
  this.submit(DataStoreMessageType.ChannelOp, envelope, localOpMetadata);
1207
1256
  }
1208
1257
 
1258
+ /**
1259
+ * Count of pending ops that have been submitted but not yet ack'd.
1260
+ * Used to compute {@link FluidDataStoreRuntime.isDirty}
1261
+ */
1262
+ private readonly pendingOpCount: { value: number } = initializePendingOpCount();
1263
+
1209
1264
  private submit(
1210
1265
  type: DataStoreMessageType,
1211
- content: any,
1266
+ content: unknown,
1212
1267
  localOpMetadata: unknown = undefined,
1213
1268
  ): void {
1214
1269
  this.verifyNotClosed();
1215
1270
  this.dataStoreContext.submitMessage(type, content, localOpMetadata);
1271
+ ++this.pendingOpCount.value;
1216
1272
  }
1217
1273
 
1218
1274
  /**
@@ -1224,27 +1280,36 @@ export class FluidDataStoreRuntime
1224
1280
  */
1225
1281
  public reSubmit(
1226
1282
  type: DataStoreMessageType,
1283
+ // TODO: use something other than `any` here (breaking change)
1284
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
1227
1285
  content: any,
1228
1286
  localOpMetadata: unknown,
1229
1287
  squash?: boolean,
1230
- ) {
1288
+ ): void {
1231
1289
  this.verifyNotClosed();
1232
1290
 
1291
+ // The op being resubmitted was not / will not be submitted, so decrement the count.
1292
+ // The calls below may result in one or more ops submitted again, which will increment the count (or not if nothing needs to be submitted anymore).
1293
+ --this.pendingOpCount.value;
1294
+
1233
1295
  switch (type) {
1234
1296
  case DataStoreMessageType.ChannelOp: {
1235
1297
  // For Operations, find the right channel and trigger resubmission on it.
1236
1298
  const envelope = content as IEnvelope;
1237
1299
  const channelContext = this.contexts.get(envelope.address);
1238
1300
  assert(!!channelContext, 0x183 /* "There should be a channel context for the op" */);
1301
+
1239
1302
  channelContext.reSubmit(envelope.contents, localOpMetadata, squash);
1240
1303
  break;
1241
1304
  }
1242
- case DataStoreMessageType.Attach:
1305
+ case DataStoreMessageType.Attach: {
1243
1306
  // For Attach messages, just submit them again.
1244
1307
  this.submit(type, content, localOpMetadata);
1245
1308
  break;
1246
- default:
1309
+ }
1310
+ default: {
1247
1311
  unreachableCase(type);
1312
+ }
1248
1313
  }
1249
1314
  }
1250
1315
 
@@ -1253,27 +1318,46 @@ export class FluidDataStoreRuntime
1253
1318
  * @param content - The content of the original message.
1254
1319
  * @param localOpMetadata - The local metadata associated with the original message.
1255
1320
  */
1256
- public rollback?(type: DataStoreMessageType, content: any, localOpMetadata: unknown) {
1321
+ public rollback?(
1322
+ type: DataStoreMessageType,
1323
+ // TODO: use something other than `any` here (breaking change)
1324
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
1325
+ content: any,
1326
+ localOpMetadata: unknown,
1327
+ ): void {
1257
1328
  this.verifyNotClosed();
1258
1329
 
1330
+ // The op being rolled back was not/will not be submitted, so decrement the count.
1331
+ --this.pendingOpCount.value;
1332
+
1259
1333
  switch (type) {
1260
1334
  case DataStoreMessageType.ChannelOp: {
1261
1335
  // For Operations, find the right channel and trigger resubmission on it.
1262
1336
  const envelope = content as IEnvelope;
1263
1337
  const channelContext = this.contexts.get(envelope.address);
1264
1338
  assert(!!channelContext, 0x2ed /* "There should be a channel context for the op" */);
1339
+
1265
1340
  channelContext.rollback(envelope.contents, localOpMetadata);
1266
1341
  break;
1267
1342
  }
1268
- default:
1343
+ default: {
1269
1344
  throw new LoggingError(`Can't rollback ${type} message`);
1345
+ }
1270
1346
  }
1271
1347
  }
1272
1348
 
1349
+ // TODO: use something other than `any` here
1350
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
1273
1351
  public async applyStashedOp(content: any): Promise<unknown> {
1352
+ // The op being applied may have been submitted in a previous session, so we increment the count here.
1353
+ // Either the ack will arrive and be processed, or that previous session's connection will end, at which point the op will be resubmitted.
1354
+ ++this.pendingOpCount.value;
1355
+
1356
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1274
1357
  const type = content?.type as DataStoreMessageType;
1275
1358
  switch (type) {
1276
1359
  case DataStoreMessageType.Attach: {
1360
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1277
1361
  const attachMessage = content.content as IAttachMessage;
1278
1362
 
1279
1363
  const flatBlobs = new Map<string, ArrayBufferLike>();
@@ -1295,39 +1379,50 @@ export class FluidDataStoreRuntime
1295
1379
  return;
1296
1380
  }
1297
1381
  case DataStoreMessageType.ChannelOp: {
1382
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1298
1383
  const envelope = content.content as IEnvelope;
1299
1384
  const channelContext = this.contexts.get(envelope.address);
1300
1385
  assert(!!channelContext, 0x184 /* "There should be a channel context for the op" */);
1301
1386
  await channelContext.getChannel();
1302
1387
  return channelContext.applyStashedOp(envelope.contents);
1303
1388
  }
1304
- default:
1389
+ default: {
1305
1390
  unreachableCase(type);
1391
+ }
1306
1392
  }
1307
1393
  }
1308
1394
 
1395
+ /**
1396
+ * Indicates the given channel is dirty from Summarizer's point of view,
1397
+ * i.e. it has local changes that need to be included in the summary.
1398
+ *
1399
+ * @remarks - If a channel's changes are rolled back or rebased away, we won't
1400
+ * clear the dirty flag set here.
1401
+ */
1309
1402
  private setChannelDirty(address: string): void {
1310
1403
  this.verifyNotClosed();
1311
1404
  this.dataStoreContext.setChannelDirty(address);
1312
1405
  }
1313
1406
 
1314
- private attachListener() {
1407
+ private attachListener(): void {
1315
1408
  this.setMaxListeners(Number.MAX_SAFE_INTEGER);
1316
1409
 
1317
1410
  // back-compat, to be removed in the future.
1318
1411
  // Added in "2.0.0-rc.2.0.0" timeframe.
1412
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any
1319
1413
  (this.dataStoreContext as any).once?.("attaching", () => {
1320
1414
  this.setAttachState(AttachState.Attaching);
1321
1415
  });
1322
1416
 
1323
1417
  // back-compat, to be removed in the future.
1324
1418
  // Added in "2.0.0-rc.2.0.0" timeframe.
1419
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any
1325
1420
  (this.dataStoreContext as any).once?.("attached", () => {
1326
1421
  this.setAttachState(AttachState.Attached);
1327
1422
  });
1328
1423
  }
1329
1424
 
1330
- private verifyNotClosed() {
1425
+ private verifyNotClosed(): void {
1331
1426
  if (this._disposed) {
1332
1427
  throw new LoggingError("Runtime is closed");
1333
1428
  }
@@ -1342,7 +1437,7 @@ export class FluidDataStoreRuntime
1342
1437
  eventName: string,
1343
1438
  channelId: string,
1344
1439
  channelType: string,
1345
- ) {
1440
+ ): void {
1346
1441
  if (this.clientDetails.type !== "summarizer" || this.localChangesTelemetryCount <= 0) {
1347
1442
  return;
1348
1443
  }
@@ -1365,7 +1460,7 @@ export class FluidDataStoreRuntime
1365
1460
 
1366
1461
  public setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void {
1367
1462
  switch (attachState) {
1368
- case AttachState.Attaching:
1463
+ case AttachState.Attaching: {
1369
1464
  /**
1370
1465
  * back-compat 0.59.1000 - Ideally, attachGraph() should have already been called making the data store
1371
1466
  * locally visible. However, before visibility state was added, this may not have been the case and data
@@ -1386,16 +1481,17 @@ export class FluidDataStoreRuntime
1386
1481
 
1387
1482
  // Mark the data store globally visible and make its child channels visible as well.
1388
1483
  this.visibilityState = VisibilityState.GloballyVisible;
1389
- this.localChannelContextQueue.forEach((channel) => {
1484
+ for (const [, channel] of this.localChannelContextQueue) {
1390
1485
  channel.makeVisible();
1391
- });
1486
+ }
1392
1487
  this.localChannelContextQueue.clear();
1393
1488
 
1394
1489
  // This promise resolution will be moved to attached event once we fix the scheduler.
1395
1490
  this.deferredAttached.resolve();
1396
1491
  this.emit("attaching");
1397
1492
  break;
1398
- case AttachState.Attached:
1493
+ }
1494
+ case AttachState.Attached: {
1399
1495
  assert(
1400
1496
  this.visibilityState === VisibilityState.GloballyVisible,
1401
1497
  0x2d2 /* "Data store should be globally visible when its attached." */,
@@ -1403,8 +1499,10 @@ export class FluidDataStoreRuntime
1403
1499
  this._attachState = AttachState.Attached;
1404
1500
  this.emit("attached");
1405
1501
  break;
1406
- default:
1502
+ }
1503
+ default: {
1407
1504
  unreachableCase(attachState, "unreached");
1505
+ }
1408
1506
  }
1409
1507
  }
1410
1508
  }
@@ -1420,9 +1518,9 @@ export class FluidDataStoreRuntime
1420
1518
  export const mixinRequestHandler = (
1421
1519
  requestHandler: (request: IRequest, runtime: FluidDataStoreRuntime) => Promise<IResponse>,
1422
1520
  Base: typeof FluidDataStoreRuntime = FluidDataStoreRuntime,
1423
- ) =>
1521
+ ): typeof FluidDataStoreRuntime =>
1424
1522
  class RuntimeWithRequestHandler extends Base {
1425
- public async request(request: IRequest) {
1523
+ public async request(request: IRequest): Promise<IResponse> {
1426
1524
  const response = await super.request(request);
1427
1525
  if (response.status === 404) {
1428
1526
  return requestHandler(request, this);
@@ -1444,9 +1542,9 @@ export const mixinSummaryHandler = (
1444
1542
  runtime: FluidDataStoreRuntime,
1445
1543
  ) => Promise<{ path: string[]; content: string } | undefined>,
1446
1544
  Base: typeof FluidDataStoreRuntime = FluidDataStoreRuntime,
1447
- ) =>
1545
+ ): typeof FluidDataStoreRuntime =>
1448
1546
  class RuntimeWithSummarizerHandler extends Base {
1449
- private addBlob(summary: ISummaryTreeWithStats, path: string[], content: string) {
1547
+ private addBlob(summary: ISummaryTreeWithStats, path: string[], content: string): void {
1450
1548
  const firstName = path.shift();
1451
1549
  if (firstName === undefined) {
1452
1550
  throw new LoggingError("Path can't be empty");
@@ -1469,7 +1567,8 @@ export const mixinSummaryHandler = (
1469
1567
  summary.summary.tree[firstName] = blob;
1470
1568
  }
1471
1569
 
1472
- async summarize(...args: any[]) {
1570
+ async summarize(...args: any[]): Promise<ISummaryTreeWithStats> {
1571
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
1473
1572
  const summary = await super.summarize(...args);
1474
1573
 
1475
1574
  try {
@@ -1477,9 +1576,9 @@ export const mixinSummaryHandler = (
1477
1576
  if (content !== undefined) {
1478
1577
  this.addBlob(summary, content.path, content.content);
1479
1578
  }
1480
- } catch (e) {
1579
+ } catch (error) {
1481
1580
  // Any error coming from app-provided handler should be marked as DataProcessingError
1482
- throw DataProcessingError.wrapIfUnrecognized(e, "mixinSummaryHandler");
1581
+ throw DataProcessingError.wrapIfUnrecognized(error, "mixinSummaryHandler");
1483
1582
  }
1484
1583
 
1485
1584
  return summary;
@@ -77,6 +77,9 @@ export class FluidObjectHandle<
77
77
  /**
78
78
  * {@inheritDoc @fluidframework/core-interfaces#IFluidHandle.get}
79
79
  */
80
+ // TODO: Return `Promise<T>` instead of `Promise<any>`.
81
+ // This was clearly the intended typing of this API, but fixing it would be a breaking change.
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
83
  public async get(): Promise<any> {
81
84
  // Note that this return works whether we received a T or a Promise<T> for this.value in the constructor.
82
85
  return this.value;
@@ -91,18 +94,19 @@ export class FluidObjectHandle<
91
94
  }
92
95
 
93
96
  this.locallyVisible = true;
94
- this.pendingHandlesToMakeVisible.forEach((handle) => {
97
+ for (const handle of this.pendingHandlesToMakeVisible) {
95
98
  handle.attachGraph();
96
- });
99
+ }
97
100
  this.pendingHandlesToMakeVisible.clear();
98
101
  this.routeContext.attachGraph();
99
102
  }
100
103
 
104
+ // eslint-disable-next-line jsdoc/require-description
101
105
  /**
102
106
  * @deprecated No replacement provided. Arbitrary handles may not serve as a bind source.
103
107
  * @privateRemarks This implementation will be moved to SharedObjectHandle once this is removed.
104
108
  */
105
- public bind(handle: IFluidHandleInternal) {
109
+ public bind(handle: IFluidHandleInternal): void {
106
110
  // If this handle is visible, attach the graph of the incoming handle as well.
107
111
  if (this.visible) {
108
112
  handle.attachGraph();