@fluidframework/container-runtime 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.5.3.2.178189

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 (277) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/blobManager.d.ts +5 -2
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +51 -22
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/connectionTelemetry.d.ts.map +1 -1
  7. package/dist/connectionTelemetry.js +8 -1
  8. package/dist/connectionTelemetry.js.map +1 -1
  9. package/dist/containerRuntime.d.ts +11 -0
  10. package/dist/containerRuntime.d.ts.map +1 -1
  11. package/dist/containerRuntime.js +59 -10
  12. package/dist/containerRuntime.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +1 -2
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +4 -3
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStoreContexts.d.ts +2 -1
  18. package/dist/dataStoreContexts.d.ts.map +1 -1
  19. package/dist/dataStoreContexts.js +2 -1
  20. package/dist/dataStoreContexts.js.map +1 -1
  21. package/dist/dataStores.d.ts +1 -1
  22. package/dist/dataStores.d.ts.map +1 -1
  23. package/dist/dataStores.js +2 -1
  24. package/dist/dataStores.js.map +1 -1
  25. package/dist/gc/garbageCollection.d.ts.map +1 -1
  26. package/dist/gc/garbageCollection.js +4 -3
  27. package/dist/gc/garbageCollection.js.map +1 -1
  28. package/dist/gc/gcDefinitions.d.ts +0 -1
  29. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  30. package/dist/gc/gcDefinitions.js.map +1 -1
  31. package/dist/gc/gcTelemetry.d.ts +1 -1
  32. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  33. package/dist/gc/gcTelemetry.js.map +1 -1
  34. package/dist/id-compressor/uuidUtilities.d.ts +0 -2
  35. package/dist/id-compressor/uuidUtilities.d.ts.map +1 -1
  36. package/dist/id-compressor/uuidUtilities.js +1 -3
  37. package/dist/id-compressor/uuidUtilities.js.map +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/metadata.d.ts +18 -0
  42. package/dist/metadata.d.ts.map +1 -0
  43. package/dist/metadata.js +7 -0
  44. package/dist/metadata.js.map +1 -0
  45. package/dist/opLifecycle/batchManager.d.ts +2 -1
  46. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  47. package/dist/opLifecycle/batchManager.js +5 -1
  48. package/dist/opLifecycle/batchManager.js.map +1 -1
  49. package/dist/opLifecycle/definitions.d.ts +11 -0
  50. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  51. package/dist/opLifecycle/definitions.js.map +1 -1
  52. package/dist/opLifecycle/index.d.ts +1 -1
  53. package/dist/opLifecycle/index.d.ts.map +1 -1
  54. package/dist/opLifecycle/index.js +2 -1
  55. package/dist/opLifecycle/index.js.map +1 -1
  56. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  57. package/dist/opLifecycle/opDecompressor.js +14 -8
  58. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  59. package/dist/opLifecycle/opGroupingManager.d.ts +1 -1
  60. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  61. package/dist/opLifecycle/opGroupingManager.js +2 -6
  62. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  63. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  64. package/dist/opLifecycle/opSplitter.js +2 -0
  65. package/dist/opLifecycle/opSplitter.js.map +1 -1
  66. package/dist/opLifecycle/outbox.d.ts +33 -2
  67. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  68. package/dist/opLifecycle/outbox.js +128 -42
  69. package/dist/opLifecycle/outbox.js.map +1 -1
  70. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  71. package/dist/opLifecycle/remoteMessageProcessor.js +3 -1
  72. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  73. package/dist/packageVersion.d.ts +1 -1
  74. package/dist/packageVersion.js +1 -1
  75. package/dist/packageVersion.js.map +1 -1
  76. package/dist/pendingStateManager.d.ts +7 -2
  77. package/dist/pendingStateManager.d.ts.map +1 -1
  78. package/dist/pendingStateManager.js +36 -21
  79. package/dist/pendingStateManager.js.map +1 -1
  80. package/dist/scheduleManager.d.ts.map +1 -1
  81. package/dist/scheduleManager.js +8 -2
  82. package/dist/scheduleManager.js.map +1 -1
  83. package/dist/summary/index.d.ts +2 -2
  84. package/dist/summary/index.d.ts.map +1 -1
  85. package/dist/summary/index.js +2 -1
  86. package/dist/summary/index.js.map +1 -1
  87. package/dist/summary/runningSummarizer.d.ts +1 -1
  88. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  89. package/dist/summary/runningSummarizer.js.map +1 -1
  90. package/dist/summary/summarizerNode/index.d.ts +1 -1
  91. package/dist/summary/summarizerNode/index.d.ts.map +1 -1
  92. package/dist/summary/summarizerNode/index.js.map +1 -1
  93. package/dist/summary/summarizerNode/summarizerNode.d.ts +32 -6
  94. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  95. package/dist/summary/summarizerNode/summarizerNode.js +90 -22
  96. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  97. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +22 -2
  98. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  99. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  100. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +17 -2
  101. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  102. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +59 -22
  103. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  104. package/dist/summary/summarizerTypes.d.ts +9 -2
  105. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  106. package/dist/summary/summarizerTypes.js.map +1 -1
  107. package/dist/summary/summaryCollection.d.ts +2 -1
  108. package/dist/summary/summaryCollection.d.ts.map +1 -1
  109. package/dist/summary/summaryCollection.js +4 -0
  110. package/dist/summary/summaryCollection.js.map +1 -1
  111. package/dist/summary/summaryFormat.d.ts +1 -0
  112. package/dist/summary/summaryFormat.d.ts.map +1 -1
  113. package/dist/summary/summaryFormat.js +2 -1
  114. package/dist/summary/summaryFormat.js.map +1 -1
  115. package/dist/summary/summaryGenerator.d.ts +13 -4
  116. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  117. package/dist/summary/summaryGenerator.js +22 -8
  118. package/dist/summary/summaryGenerator.js.map +1 -1
  119. package/dist/summary/summaryManager.d.ts +2 -1
  120. package/dist/summary/summaryManager.d.ts.map +1 -1
  121. package/dist/summary/summaryManager.js.map +1 -1
  122. package/lib/blobManager.d.ts +5 -2
  123. package/lib/blobManager.d.ts.map +1 -1
  124. package/lib/blobManager.js +51 -22
  125. package/lib/blobManager.js.map +1 -1
  126. package/lib/connectionTelemetry.d.ts.map +1 -1
  127. package/lib/connectionTelemetry.js +8 -1
  128. package/lib/connectionTelemetry.js.map +1 -1
  129. package/lib/containerRuntime.d.ts +11 -0
  130. package/lib/containerRuntime.d.ts.map +1 -1
  131. package/lib/containerRuntime.js +62 -13
  132. package/lib/containerRuntime.js.map +1 -1
  133. package/lib/dataStoreContext.d.ts +1 -2
  134. package/lib/dataStoreContext.d.ts.map +1 -1
  135. package/lib/dataStoreContext.js +4 -3
  136. package/lib/dataStoreContext.js.map +1 -1
  137. package/lib/dataStoreContexts.d.ts +2 -1
  138. package/lib/dataStoreContexts.d.ts.map +1 -1
  139. package/lib/dataStoreContexts.js +2 -1
  140. package/lib/dataStoreContexts.js.map +1 -1
  141. package/lib/dataStores.d.ts +1 -1
  142. package/lib/dataStores.d.ts.map +1 -1
  143. package/lib/dataStores.js +2 -1
  144. package/lib/dataStores.js.map +1 -1
  145. package/lib/gc/garbageCollection.d.ts.map +1 -1
  146. package/lib/gc/garbageCollection.js +2 -1
  147. package/lib/gc/garbageCollection.js.map +1 -1
  148. package/lib/gc/gcDefinitions.d.ts +0 -1
  149. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  150. package/lib/gc/gcDefinitions.js.map +1 -1
  151. package/lib/gc/gcTelemetry.d.ts +1 -1
  152. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  153. package/lib/gc/gcTelemetry.js.map +1 -1
  154. package/lib/id-compressor/uuidUtilities.d.ts +0 -2
  155. package/lib/id-compressor/uuidUtilities.d.ts.map +1 -1
  156. package/lib/id-compressor/uuidUtilities.js +1 -3
  157. package/lib/id-compressor/uuidUtilities.js.map +1 -1
  158. package/lib/index.d.ts +1 -1
  159. package/lib/index.d.ts.map +1 -1
  160. package/lib/index.js.map +1 -1
  161. package/lib/metadata.d.ts +18 -0
  162. package/lib/metadata.d.ts.map +1 -0
  163. package/lib/metadata.js +6 -0
  164. package/lib/metadata.js.map +1 -0
  165. package/lib/opLifecycle/batchManager.d.ts +2 -1
  166. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  167. package/lib/opLifecycle/batchManager.js +5 -1
  168. package/lib/opLifecycle/batchManager.js.map +1 -1
  169. package/lib/opLifecycle/definitions.d.ts +11 -0
  170. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  171. package/lib/opLifecycle/definitions.js.map +1 -1
  172. package/lib/opLifecycle/index.d.ts +1 -1
  173. package/lib/opLifecycle/index.d.ts.map +1 -1
  174. package/lib/opLifecycle/index.js +1 -1
  175. package/lib/opLifecycle/index.js.map +1 -1
  176. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  177. package/lib/opLifecycle/opDecompressor.js +14 -8
  178. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  179. package/lib/opLifecycle/opGroupingManager.d.ts +1 -1
  180. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  181. package/lib/opLifecycle/opGroupingManager.js +2 -6
  182. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  183. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  184. package/lib/opLifecycle/opSplitter.js +2 -0
  185. package/lib/opLifecycle/opSplitter.js.map +1 -1
  186. package/lib/opLifecycle/outbox.d.ts +33 -2
  187. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  188. package/lib/opLifecycle/outbox.js +126 -41
  189. package/lib/opLifecycle/outbox.js.map +1 -1
  190. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  191. package/lib/opLifecycle/remoteMessageProcessor.js +3 -1
  192. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  193. package/lib/packageVersion.d.ts +1 -1
  194. package/lib/packageVersion.js +1 -1
  195. package/lib/packageVersion.js.map +1 -1
  196. package/lib/pendingStateManager.d.ts +7 -2
  197. package/lib/pendingStateManager.d.ts.map +1 -1
  198. package/lib/pendingStateManager.js +36 -21
  199. package/lib/pendingStateManager.js.map +1 -1
  200. package/lib/scheduleManager.d.ts.map +1 -1
  201. package/lib/scheduleManager.js +8 -2
  202. package/lib/scheduleManager.js.map +1 -1
  203. package/lib/summary/index.d.ts +2 -2
  204. package/lib/summary/index.d.ts.map +1 -1
  205. package/lib/summary/index.js +1 -1
  206. package/lib/summary/index.js.map +1 -1
  207. package/lib/summary/runningSummarizer.d.ts +1 -1
  208. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  209. package/lib/summary/runningSummarizer.js.map +1 -1
  210. package/lib/summary/summarizerNode/index.d.ts +1 -1
  211. package/lib/summary/summarizerNode/index.d.ts.map +1 -1
  212. package/lib/summary/summarizerNode/index.js.map +1 -1
  213. package/lib/summary/summarizerNode/summarizerNode.d.ts +32 -6
  214. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  215. package/lib/summary/summarizerNode/summarizerNode.js +90 -22
  216. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  217. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +22 -2
  218. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  219. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  220. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +17 -2
  221. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  222. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +58 -21
  223. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  224. package/lib/summary/summarizerTypes.d.ts +9 -2
  225. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  226. package/lib/summary/summarizerTypes.js.map +1 -1
  227. package/lib/summary/summaryCollection.d.ts +2 -1
  228. package/lib/summary/summaryCollection.d.ts.map +1 -1
  229. package/lib/summary/summaryCollection.js +4 -0
  230. package/lib/summary/summaryCollection.js.map +1 -1
  231. package/lib/summary/summaryFormat.d.ts +1 -0
  232. package/lib/summary/summaryFormat.d.ts.map +1 -1
  233. package/lib/summary/summaryFormat.js +2 -1
  234. package/lib/summary/summaryFormat.js.map +1 -1
  235. package/lib/summary/summaryGenerator.d.ts +13 -4
  236. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  237. package/lib/summary/summaryGenerator.js +20 -7
  238. package/lib/summary/summaryGenerator.js.map +1 -1
  239. package/lib/summary/summaryManager.d.ts +2 -1
  240. package/lib/summary/summaryManager.d.ts.map +1 -1
  241. package/lib/summary/summaryManager.js.map +1 -1
  242. package/package.json +20 -19
  243. package/src/blobManager.ts +68 -27
  244. package/src/connectionTelemetry.ts +10 -1
  245. package/src/containerRuntime.ts +74 -17
  246. package/src/dataStoreContext.ts +12 -5
  247. package/src/dataStoreContexts.ts +4 -2
  248. package/src/dataStores.ts +8 -3
  249. package/src/gc/garbageCollection.ts +2 -1
  250. package/src/gc/gcDefinitions.ts +0 -1
  251. package/src/gc/gcTelemetry.ts +1 -1
  252. package/src/id-compressor/uuidUtilities.ts +1 -4
  253. package/src/index.ts +2 -0
  254. package/src/metadata.ts +19 -0
  255. package/src/opLifecycle/README.md +20 -0
  256. package/src/opLifecycle/batchManager.ts +9 -1
  257. package/src/opLifecycle/definitions.ts +11 -0
  258. package/src/opLifecycle/index.ts +1 -1
  259. package/src/opLifecycle/opDecompressor.ts +41 -13
  260. package/src/opLifecycle/opGroupingManager.ts +14 -7
  261. package/src/opLifecycle/opSplitter.ts +3 -1
  262. package/src/opLifecycle/outbox.ts +163 -49
  263. package/src/opLifecycle/remoteMessageProcessor.ts +5 -1
  264. package/src/packageVersion.ts +1 -1
  265. package/src/pendingStateManager.ts +56 -41
  266. package/src/scheduleManager.ts +15 -6
  267. package/src/summary/index.ts +3 -1
  268. package/src/summary/runningSummarizer.ts +1 -1
  269. package/src/summary/summarizerNode/index.ts +1 -0
  270. package/src/summary/summarizerNode/summarizerNode.ts +107 -25
  271. package/src/summary/summarizerNode/summarizerNodeUtils.ts +25 -2
  272. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +63 -20
  273. package/src/summary/summarizerTypes.ts +12 -2
  274. package/src/summary/summaryCollection.ts +8 -3
  275. package/src/summary/summaryFormat.ts +5 -1
  276. package/src/summary/summaryGenerator.ts +31 -8
  277. package/src/summary/summaryManager.ts +2 -1
@@ -2,8 +2,9 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { ITelemetryBaseLogger, ITelemetryGenericEvent } from "@fluidframework/common-definitions";
6
5
  import {
6
+ ITelemetryBaseLogger,
7
+ ITelemetryGenericEvent,
7
8
  FluidObject,
8
9
  IFluidHandle,
9
10
  IFluidHandleContext,
@@ -28,11 +29,11 @@ import {
28
29
  import {
29
30
  assert,
30
31
  delay,
31
- LazyPromise,
32
32
  Trace,
33
33
  TypedEventEmitter,
34
34
  unreachableCase,
35
35
  } from "@fluidframework/common-utils";
36
+ import { LazyPromise } from "@fluidframework/core-utils";
36
37
  import {
37
38
  ChildLogger,
38
39
  raiseConnectedEvent,
@@ -113,7 +114,11 @@ import { v4 as uuid } from "uuid";
113
114
  import { ContainerFluidHandleContext } from "./containerHandleContext";
114
115
  import { FluidDataStoreRegistry } from "./dataStoreRegistry";
115
116
  import { ReportOpPerfTelemetry, IPerfSignalReport } from "./connectionTelemetry";
116
- import { IPendingLocalState, PendingStateManager } from "./pendingStateManager";
117
+ import {
118
+ IPendingBatchMessage,
119
+ IPendingLocalState,
120
+ PendingStateManager,
121
+ } from "./pendingStateManager";
117
122
  import { pkgVersion } from "./packageVersion";
118
123
  import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
119
124
  import { DataStores, getSummaryForDatastores } from "./dataStores";
@@ -149,6 +154,8 @@ import {
149
154
  ISummarizerRuntime,
150
155
  IRefreshSummaryAckOptions,
151
156
  RunWhileConnectedCoordinator,
157
+ IGenerateSummaryTreeResult,
158
+ RetriableSummaryError,
152
159
  } from "./summary";
153
160
  import { formExponentialFn, Throttler } from "./throttler";
154
161
  import {
@@ -173,8 +180,10 @@ import {
173
180
  OpSplitter,
174
181
  RemoteMessageProcessor,
175
182
  OpGroupingManager,
183
+ getLongStack,
176
184
  } from "./opLifecycle";
177
185
  import { DeltaManagerSummarizerProxy } from "./deltaManagerSummarizerProxy";
186
+ import { IBatchMetadata } from "./metadata";
178
187
 
179
188
  export enum ContainerMessageType {
180
189
  // An op to be delivered to store
@@ -831,6 +840,11 @@ export class ContainerRuntime
831
840
  }
832
841
 
833
842
  public get closeFn(): (error?: ICriticalContainerError) => void {
843
+ if (this._summarizer !== undefined) {
844
+ // In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
845
+ return this.disposeFn;
846
+ }
847
+
834
848
  // Also call disposeFn to retain functionality of runtime being disposed on close
835
849
  return (error?: ICriticalContainerError) => {
836
850
  this.context.closeFn(error);
@@ -952,6 +966,12 @@ export class ContainerRuntime
952
966
  private readonly disableAttachReorder: boolean | undefined;
953
967
  private readonly summaryStateUpdateMethod: string | undefined;
954
968
  private readonly closeSummarizerDelayMs: number;
969
+ /**
970
+ * If true, summary generated is validate before uploading it to the server. With single commit summaries,
971
+ * summaries will be accepted once uploaded, so they should be validated before upload. However, this can
972
+ * currently be controlled via a feature flag as its a new functionality.
973
+ */
974
+ private readonly validateSummaryBeforeUpload: boolean;
955
975
 
956
976
  private readonly defaultTelemetrySignalSampleCount = 100;
957
977
  private _perfSignalData: IPerfSignalReport = {
@@ -1206,7 +1226,6 @@ export class ContainerRuntime
1206
1226
  getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
1207
1227
  getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
1208
1228
  readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
1209
- getContainerDiagnosticId: () => this.context.id,
1210
1229
  // GC runs in summarizer client and needs access to the real (non-proxy) active information. The proxy
1211
1230
  // delta manager would always return false for summarizer client.
1212
1231
  activeConnection: () => this.innerDeltaManager.active,
@@ -1299,7 +1318,7 @@ export class ContainerRuntime
1299
1318
  close: this.closeFn,
1300
1319
  connected: () => this.connected,
1301
1320
  reSubmit: this.reSubmit.bind(this),
1302
- orderSequentially: this.orderSequentially.bind(this),
1321
+ reSubmitBatch: this.reSubmitBatch.bind(this),
1303
1322
  },
1304
1323
  pendingRuntimeState?.pending,
1305
1324
  );
@@ -1328,6 +1347,7 @@ export class ContainerRuntime
1328
1347
  compressionOptions,
1329
1348
  maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
1330
1349
  disablePartialFlush: disablePartialFlush === true,
1350
+ enableGroupedBatching: this.groupedBatchingEnabled,
1331
1351
  },
1332
1352
  logger: this.mc.logger,
1333
1353
  groupingManager: opGroupingManager,
@@ -1335,6 +1355,9 @@ export class ContainerRuntime
1335
1355
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
1336
1356
  clientSequenceNumber: this._processedClientSequenceNumber,
1337
1357
  }),
1358
+ reSubmit: this.reSubmit.bind(this),
1359
+ opReentrancy: () => this.ensureNoDataModelChangesCalls > 0,
1360
+ closeContainer: this.closeFn,
1338
1361
  });
1339
1362
 
1340
1363
  this.context.quorum.on("removeMember", (clientId: string) => {
@@ -1342,12 +1365,15 @@ export class ContainerRuntime
1342
1365
  });
1343
1366
 
1344
1367
  this.summaryStateUpdateMethod = this.mc.config.getString(
1345
- "Fluid.ContainerRuntime.Test.SummaryStateUpdateMethod",
1368
+ "Fluid.ContainerRuntime.Test.SummaryStateUpdateMethodV2",
1346
1369
  );
1347
1370
  const closeSummarizerDelayOverride = this.mc.config.getNumber(
1348
1371
  "Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs",
1349
1372
  );
1350
1373
  this.closeSummarizerDelayMs = closeSummarizerDelayOverride ?? defaultCloseSummarizerDelayMs;
1374
+ this.validateSummaryBeforeUpload =
1375
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.Test.ValidateSummaryBeforeUpload") ??
1376
+ false;
1351
1377
 
1352
1378
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
1353
1379
 
@@ -2024,7 +2050,7 @@ export class ContainerRuntime
2024
2050
  local,
2025
2051
  type: message.type,
2026
2052
  contentType: typeof message.contents,
2027
- batch: message.metadata?.batch,
2053
+ batch: (message.metadata as IBatchMetadata | undefined)?.batch,
2028
2054
  compression: message.compression,
2029
2055
  },
2030
2056
  );
@@ -2808,7 +2834,7 @@ export class ContainerRuntime
2808
2834
  summaryNumber,
2809
2835
  ...partialStats,
2810
2836
  };
2811
- const generateSummaryData = {
2837
+ const generateSummaryData: Omit<IGenerateSummaryTreeResult, "stage" | "error"> = {
2812
2838
  referenceSequenceNumber: summaryRefSeqNum,
2813
2839
  minimumSequenceNumber,
2814
2840
  summaryTree,
@@ -2817,6 +2843,21 @@ export class ContainerRuntime
2817
2843
  forcedFullTree,
2818
2844
  } as const;
2819
2845
 
2846
+ // If validateSummaryBeforeUpload is true, validate that the summary generated by the summarizer nodes is
2847
+ // correct before this summary is uploaded.
2848
+ if (this.validateSummaryBeforeUpload) {
2849
+ const validateResult = this.summarizerNode.validateSummary();
2850
+ if (!validateResult.success) {
2851
+ const { success, ...loggingProps } = validateResult;
2852
+ const error = new RetriableSummaryError(
2853
+ validateResult.reason,
2854
+ validateResult.retryAfterSeconds,
2855
+ { ...loggingProps },
2856
+ );
2857
+ return { stage: "base", ...generateSummaryData, error };
2858
+ }
2859
+ }
2860
+
2820
2861
  continueResult = checkContinue();
2821
2862
  if (!continueResult.continue) {
2822
2863
  return { stage: "generate", ...generateSummaryData, error: continueResult.error };
@@ -2894,7 +2935,15 @@ export class ContainerRuntime
2894
2935
  submitOpDuration: trace.trace().duration,
2895
2936
  } as const;
2896
2937
 
2897
- this.summarizerNode.completeSummary(handle);
2938
+ try {
2939
+ // If validateSummaryBeforeUpload is false, the summary should be validated in this step.
2940
+ this.summarizerNode.completeSummary(
2941
+ handle,
2942
+ !this.validateSummaryBeforeUpload /* validate */,
2943
+ );
2944
+ } catch (error) {
2945
+ return { stage: "upload", ...uploadData, error };
2946
+ }
2898
2947
  return submitData;
2899
2948
  } finally {
2900
2949
  // Cleanup wip summary in case of failure
@@ -3064,6 +3113,9 @@ export class ContainerRuntime
3064
3113
  this.disableAttachReorder !== true
3065
3114
  ) {
3066
3115
  this.outbox.submitAttach(message);
3116
+ } else if (type === ContainerMessageType.BlobAttach) {
3117
+ // BlobAttach ops must have their metadata visible and cannot be grouped (see opGroupingManager.ts)
3118
+ this.outbox.submitBlobAttach(message);
3067
3119
  } else {
3068
3120
  this.outbox.submit(message);
3069
3121
  }
@@ -3157,7 +3209,7 @@ export class ContainerRuntime
3157
3209
  this.mc.logger.sendTelemetryEvent(
3158
3210
  { eventName: "OpReentry" },
3159
3211
  // We need to capture the call stack in order to inspect the source of this usage pattern
3160
- new UsageError(errorMessage),
3212
+ getLongStack(() => new UsageError(errorMessage)),
3161
3213
  );
3162
3214
  this.opReentryCallsToReport--;
3163
3215
  }
@@ -3180,14 +3232,19 @@ export class ContainerRuntime
3180
3232
  }
3181
3233
  }
3182
3234
 
3183
- private reSubmit(
3184
- content: string,
3185
- localOpMetadata: unknown,
3186
- opMetadata: Record<string, unknown> | undefined,
3187
- ) {
3235
+ private reSubmitBatch(batch: IPendingBatchMessage[]) {
3236
+ this.orderSequentially(() => {
3237
+ for (const message of batch) {
3238
+ this.reSubmit(message);
3239
+ }
3240
+ });
3241
+ this.flush();
3242
+ }
3243
+
3244
+ private reSubmit(message: IPendingBatchMessage) {
3188
3245
  // Need to parse from string for back-compat
3189
- const { contents, type } = this.parseOpContent(content);
3190
- this.reSubmitCore(type, contents, localOpMetadata, opMetadata);
3246
+ const { contents, type } = this.parseOpContent(message.content);
3247
+ this.reSubmitCore(type, contents, message.localOpMetadata, message.opMetadata);
3191
3248
  }
3192
3249
 
3193
3250
  /**
@@ -3,15 +3,22 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, ITelemetryProperties } from "@fluidframework/common-definitions";
7
- import { FluidObject, IRequest, IResponse, IFluidHandle } from "@fluidframework/core-interfaces";
6
+ import {
7
+ IDisposable,
8
+ FluidObject,
9
+ IRequest,
10
+ IResponse,
11
+ IFluidHandle,
12
+ ITelemetryProperties,
13
+ } from "@fluidframework/core-interfaces";
8
14
  import {
9
15
  IAudience,
10
16
  IDeltaManager,
11
17
  AttachState,
12
18
  ILoaderOptions,
13
19
  } from "@fluidframework/container-definitions";
14
- import { assert, Deferred, LazyPromise, TypedEventEmitter } from "@fluidframework/common-utils";
20
+ import { assert, Deferred, TypedEventEmitter } from "@fluidframework/common-utils";
21
+ import { LazyPromise } from "@fluidframework/core-utils";
15
22
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
16
23
  import { BlobTreeEntry, readAndParse } from "@fluidframework/driver-utils";
17
24
  import {
@@ -390,7 +397,7 @@ export abstract class FluidDataStoreContext
390
397
  packageName: packagePathToTelemetryProperty(this.pkg),
391
398
  });
392
399
  this.channelDeferred?.reject(errorWrapped);
393
- this.logger.sendErrorEvent({ eventName: "RealizeError" }, errorWrapped);
400
+ this.mc.logger.sendErrorEvent({ eventName: "RealizeError" }, errorWrapped);
394
401
  });
395
402
  }
396
403
  return this.channelDeferred.promise;
@@ -780,7 +787,7 @@ export abstract class FluidDataStoreContext
780
787
  this.channelDeferred.resolve(this.channel);
781
788
  } catch (error) {
782
789
  this.channelDeferred?.reject(error);
783
- this.logger.sendErrorEvent(
790
+ this.mc.logger.sendErrorEvent(
784
791
  {
785
792
  eventName: "BindRuntimeError",
786
793
  fluidDataStoreId: {
@@ -3,8 +3,10 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, ITelemetryBaseLogger } from "@fluidframework/common-definitions";
7
- import { assert, Deferred, Lazy } from "@fluidframework/common-utils";
6
+ import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
7
+ import { assert, Deferred } from "@fluidframework/common-utils";
8
+ import { Lazy } from "@fluidframework/core-utils";
9
+ import { IDisposable } from "@fluidframework/core-interfaces";
8
10
  import { ChildLogger, ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
9
11
  import { FluidDataStoreContext, LocalFluidDataStoreContext } from "./dataStoreContext";
10
12
 
package/src/dataStores.ts CHANGED
@@ -3,12 +3,16 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryBaseLogger, IDisposable } from "@fluidframework/common-definitions";
7
6
  import {
8
7
  DataCorruptionError,
9
8
  extractSafePropertiesFromMessage,
10
9
  } from "@fluidframework/container-utils";
11
- import { IFluidHandle, IRequest } from "@fluidframework/core-interfaces";
10
+ import {
11
+ ITelemetryBaseLogger,
12
+ IDisposable,
13
+ IFluidHandle,
14
+ IRequest,
15
+ } from "@fluidframework/core-interfaces";
12
16
  import { FluidObjectHandle } from "@fluidframework/datastore";
13
17
  import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
14
18
  import {
@@ -46,7 +50,8 @@ import {
46
50
  } from "@fluidframework/telemetry-utils";
47
51
  import { AttachState } from "@fluidframework/container-definitions";
48
52
  import { buildSnapshotTree } from "@fluidframework/driver-utils";
49
- import { assert, Lazy } from "@fluidframework/common-utils";
53
+ import { assert } from "@fluidframework/common-utils";
54
+ import { Lazy } from "@fluidframework/core-utils";
50
55
  import { v4 as uuid } from "uuid";
51
56
  import { DataStoreContexts } from "./dataStoreContexts";
52
57
  import {
@@ -3,7 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { LazyPromise, Timer } from "@fluidframework/common-utils";
6
+ import { Timer } from "@fluidframework/common-utils";
7
+ import { LazyPromise } from "@fluidframework/core-utils";
7
8
  import { ClientSessionExpiredError, DataProcessingError } from "@fluidframework/container-utils";
8
9
  import { IRequestHeader } from "@fluidframework/core-interfaces";
9
10
  import {
@@ -263,7 +263,6 @@ export interface IGarbageCollectorCreateParams {
263
263
  readonly getLastSummaryTimestampMs: () => number | undefined;
264
264
  readonly readAndParseBlob: ReadAndParseBlob;
265
265
  readonly activeConnection: () => boolean;
266
- readonly getContainerDiagnosticId: () => string;
267
266
  }
268
267
 
269
268
  export interface IGCRuntimeOptions {
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryGenericEvent } from "@fluidframework/common-definitions";
6
+ import { ITelemetryGenericEvent } from "@fluidframework/core-interfaces";
7
7
  import { IGarbageCollectionData } from "@fluidframework/runtime-definitions";
8
8
  import { packagePathToTelemetryProperty } from "@fluidframework/runtime-utils";
9
9
  import {
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { assert } from "@fluidframework/common-utils";
7
7
  import { StableId, UuidString } from "@fluidframework/runtime-definitions";
8
- import { v4, NIL } from "uuid";
8
+ import { v4 } from "uuid";
9
9
 
10
10
  const hexadecimalCharCodes = Array.from("09afAF").map((c) => c.charCodeAt(0)) as [
11
11
  zero: number,
@@ -24,9 +24,6 @@ function isHexadecimalCharacter(charCode: number): boolean {
24
24
  );
25
25
  }
26
26
 
27
- /** The null (lowest/all-zeros) UUID */
28
- export const nilUuid = assertIsUuidString(NIL);
29
-
30
27
  /**
31
28
  * Asserts that the given string is a UUID
32
29
  */
package/src/index.ts CHANGED
@@ -67,6 +67,8 @@ export {
67
67
  OpActionEventListener,
68
68
  OpActionEventName,
69
69
  ICancellableSummarizerController,
70
+ SubmitSummaryFailureData,
71
+ SummaryStage,
70
72
  } from "./summary";
71
73
  export { IChunkedOp, unpackRuntimeMessage } from "./opLifecycle";
72
74
  export { generateStableId, isStableId, assertIsStableId } from "./id-compressor";
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Batching makes assumptions about what might be on the metadata. This interface codifies those assumptions, but does not validate them.
8
+ */
9
+ export interface IBatchMetadata {
10
+ batch?: boolean;
11
+ }
12
+
13
+ /**
14
+ * Blob handling makes assumptions about what might be on the metadata. This interface codifies those assumptions, but does not validate them.
15
+ */
16
+ export interface IBlobMetadata {
17
+ blobId?: string;
18
+ localId?: string;
19
+ }
@@ -51,6 +51,26 @@ and verifying that the following expectation changes won't have any effects:
51
51
  - client sequence numbers on batch messages can only be used to order messages with the same sequenceNumber
52
52
  - requires all ops to be processed by runtime layer (version "2.0.0-internal.1.2.0" or later https://github.com/microsoft/FluidFramework/pull/11832)
53
53
 
54
+ Grouped batching may become problematic for batches which contain reentrant ops. This is the case when changes are made to a DDS inside a DDS 'onChanged' event handler. This means that the reentrant op will have a different reference sequence number than the rest of the ops in the batch, resulting in a different view of the state of the data model.
55
+
56
+ Therefore, when grouped batching is enabled, all batches with reentrant ops are rebased to the current reference sequence number and resubmitted to the data stores so that all ops are in agreement about the state of the data model and ensure eventual consistency.
57
+
58
+ ### How to enable
59
+
60
+ **This feature is disabled by default, currently considered experimental and not ready for production usage.**
61
+
62
+ If all prerequisites in the previous section are met, enabling the feature can be done via the `IContainerRuntimeOptions` as following:
63
+
64
+ ```
65
+ const runtimeOptions: IContainerRuntimeOptions = {
66
+ (...)
67
+ enableGroupedBatching: true,
68
+ (...)
69
+   }
70
+ ```
71
+
72
+ In case of emergency grouped batching can be disabled at runtime, using feature gates. If `"Fluid.ContainerRuntime.DisableGroupedBatching"` is set to `true`, it will disable grouped batching if enabled from `IContainerRuntimeOptions` in the code.
73
+
54
74
  ## Chunking for compression
55
75
 
56
76
  **Op chunking for compression targets payloads which exceed the max batch size after compression.** So, only payloads which are already compressed. By default, the feature is enabled.
@@ -29,6 +29,7 @@ const opOverhead = 200;
29
29
  export class BatchManager {
30
30
  private pendingBatch: BatchMessage[] = [];
31
31
  private batchContentSize = 0;
32
+ private hasReentrantOps = false;
32
33
 
33
34
  public get length() {
34
35
  return this.pendingBatch.length;
@@ -54,9 +55,14 @@ export class BatchManager {
54
55
 
55
56
  constructor(public readonly options: IBatchManagerOptions) {}
56
57
 
57
- public push(message: BatchMessage, currentClientSequenceNumber?: number): boolean {
58
+ public push(
59
+ message: BatchMessage,
60
+ reentrant: boolean,
61
+ currentClientSequenceNumber?: number,
62
+ ): boolean {
58
63
  const contentSize = this.batchContentSize + (message.contents?.length ?? 0);
59
64
  const opCount = this.pendingBatch.length;
65
+ this.hasReentrantOps = this.hasReentrantOps || reentrant;
60
66
 
61
67
  // Attempt to estimate batch size, aka socket message size.
62
68
  // Each op has pretty large envelope, estimating to be 200 bytes.
@@ -100,11 +106,13 @@ export class BatchManager {
100
106
  content: this.pendingBatch,
101
107
  contentSizeInBytes: this.batchContentSize,
102
108
  referenceSequenceNumber: this.referenceSequenceNumber,
109
+ hasReentrantOps: this.hasReentrantOps,
103
110
  };
104
111
 
105
112
  this.pendingBatch = [];
106
113
  this.batchContentSize = 0;
107
114
  this.clientSequenceNumber = undefined;
115
+ this.hasReentrantOps = false;
108
116
 
109
117
  return addBatchMetadata(batch);
110
118
  }
@@ -34,6 +34,17 @@ export interface IBatch {
34
34
  * The reference sequence number for the batch
35
35
  */
36
36
  readonly referenceSequenceNumber: number | undefined;
37
+ /**
38
+ * Wether or not the batch contains at least one op which was produced as the result
39
+ * of processing another op. This means that the batch must be rebased before
40
+ * submitted, to ensure that all ops have the same reference sequence numbers and a
41
+ * consistent view of the data model. This happens when the op is created within a
42
+ * 'changed' event handler of a DDS and will have a different reference sequence number
43
+ * than the rest of the ops in the batch, meaning that it has a different view of the
44
+ * state of the data model, therefore all ops must be resubmitted and rebased to the current
45
+ * reference sequence number to be in agreement about the data model state.
46
+ */
47
+ readonly hasReentrantOps?: boolean;
37
48
  }
38
49
 
39
50
  export interface IBatchCheckpoint {
@@ -11,7 +11,7 @@ export {
11
11
  IChunkedOp,
12
12
  IMessageProcessingResult,
13
13
  } from "./definitions";
14
- export { Outbox } from "./outbox";
14
+ export { Outbox, getLongStack } from "./outbox";
15
15
  export { OpCompressor } from "./opCompressor";
16
16
  export { OpDecompressor } from "./opDecompressor";
17
17
  export { OpSplitter, splitOp } from "./opSplitter";
@@ -8,8 +8,16 @@ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"
8
8
  import { assert, IsoBuffer, Uint8ArrayToString } from "@fluidframework/common-utils";
9
9
  import { ChildLogger, ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
10
10
  import { CompressionAlgorithms } from "../containerRuntime";
11
+ import { IBatchMetadata } from "../metadata";
11
12
  import { IMessageProcessingResult } from "./definitions";
12
13
 
14
+ /**
15
+ * Compression makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.
16
+ */
17
+ interface IPackedContentsContents {
18
+ packedContents: string;
19
+ }
20
+
13
21
  /**
14
22
  * State machine that "unrolls" contents of compressed batches of ops after decompressing them.
15
23
  * This class relies on some implicit contracts defined below:
@@ -34,7 +42,10 @@ export class OpDecompressor {
34
42
  0x511 /* Only lz4 compression is supported */,
35
43
  );
36
44
 
37
- if (message.metadata?.batch === true && this.isCompressed(message)) {
45
+ if (
46
+ (message.metadata as IBatchMetadata | undefined)?.batch === true &&
47
+ this.isCompressed(message)
48
+ ) {
38
49
  // Beginning of a compressed batch
39
50
  assert(this.activeBatch === false, 0x4b8 /* shouldn't have multiple active batches */);
40
51
  if (message.compression) {
@@ -47,7 +58,10 @@ export class OpDecompressor {
47
58
 
48
59
  this.activeBatch = true;
49
60
 
50
- const contents = IsoBuffer.from(message.contents.packedContents, "base64");
61
+ const contents = IsoBuffer.from(
62
+ (message.contents as IPackedContentsContents).packedContents,
63
+ "base64",
64
+ );
51
65
  const decompressedMessage = decompress(contents);
52
66
  const intoString = Uint8ArrayToString(decompressedMessage);
53
67
  const asObj = JSON.parse(intoString);
@@ -61,7 +75,7 @@ export class OpDecompressor {
61
75
 
62
76
  if (
63
77
  this.rootMessageContents !== undefined &&
64
- message.metadata?.batch === undefined &&
78
+ (message.metadata as IBatchMetadata | undefined)?.batch === undefined &&
65
79
  this.activeBatch
66
80
  ) {
67
81
  assert(message.contents === undefined, 0x512 /* Expecting empty message */);
@@ -73,7 +87,10 @@ export class OpDecompressor {
73
87
  };
74
88
  }
75
89
 
76
- if (this.rootMessageContents !== undefined && message.metadata?.batch === false) {
90
+ if (
91
+ this.rootMessageContents !== undefined &&
92
+ (message.metadata as IBatchMetadata | undefined)?.batch === false
93
+ ) {
77
94
  // End of compressed batch
78
95
  const returnMessage = newMessage(
79
96
  message,
@@ -90,14 +107,20 @@ export class OpDecompressor {
90
107
  };
91
108
  }
92
109
 
93
- if (message.metadata?.batch === undefined && this.isCompressed(message)) {
110
+ if (
111
+ (message.metadata as IBatchMetadata | undefined)?.batch === undefined &&
112
+ this.isCompressed(message)
113
+ ) {
94
114
  // Single compressed message
95
115
  assert(
96
116
  this.activeBatch === false,
97
117
  0x4ba /* shouldn't receive compressed message in middle of a batch */,
98
118
  );
99
119
 
100
- const contents = IsoBuffer.from(message.contents.packedContents, "base64");
120
+ const contents = IsoBuffer.from(
121
+ (message.contents as IPackedContentsContents).packedContents,
122
+ "base64",
123
+ );
101
124
  const decompressedMessage = decompress(contents);
102
125
  const intoString = new TextDecoder().decode(decompressedMessage);
103
126
  const asObj = JSON.parse(intoString);
@@ -135,16 +158,19 @@ export class OpDecompressor {
135
158
  message.contents !== null &&
136
159
  typeof message.contents === "object" &&
137
160
  Object.keys(message.contents).length === 1 &&
138
- message.contents?.packedContents !== undefined &&
139
- typeof message.contents?.packedContents === "string" &&
140
- message.contents.packedContents.length > 0 &&
141
- IsoBuffer.from(message.contents.packedContents, "base64").toString("base64") ===
142
- message.contents.packedContents
161
+ typeof (message.contents as { packedContents?: unknown }).packedContents ===
162
+ "string" &&
163
+ (message.contents as IPackedContentsContents).packedContents.length > 0 &&
164
+ IsoBuffer.from(
165
+ (message.contents as IPackedContentsContents).packedContents,
166
+ "base64",
167
+ ).toString("base64") ===
168
+ (message.contents as IPackedContentsContents).packedContents
143
169
  ) {
144
170
  this.logger.sendTelemetryEvent({
145
171
  eventName: "LegacyCompression",
146
172
  type: message.type,
147
- batch: message.metadata?.batch,
173
+ batch: (message.metadata as IBatchMetadata | undefined)?.batch,
148
174
  });
149
175
  return true;
150
176
  }
@@ -164,5 +190,7 @@ const newMessage = (
164
190
  ...originalMessage,
165
191
  contents,
166
192
  compression: undefined,
167
- metadata: { ...originalMessage.metadata },
193
+ // TODO: It should already be the case that we're not modifying any metadata, not clear if/why this shallow clone should be required.
194
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
195
+ metadata: { ...(originalMessage.metadata as any) },
168
196
  });
@@ -8,6 +8,14 @@ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"
8
8
  import { ContainerMessageType } from "..";
9
9
  import { IBatch } from "./definitions";
10
10
 
11
+ /**
12
+ * Grouping makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.
13
+ */
14
+ interface IGroupedBatchMessageContents {
15
+ type: typeof OpGroupingManager.groupedBatchOp;
16
+ contents: IGroupedMessage[];
17
+ }
18
+
11
19
  interface IGroupedMessage {
12
20
  contents?: unknown;
13
21
  metadata?: Record<string, unknown>;
@@ -15,7 +23,7 @@ interface IGroupedMessage {
15
23
  }
16
24
 
17
25
  export class OpGroupingManager {
18
- static groupedBatchOp = "groupedBatch";
26
+ static readonly groupedBatchOp = "groupedBatch";
19
27
 
20
28
  constructor(private readonly groupedBatchingEnabled: boolean) {}
21
29
 
@@ -25,10 +33,6 @@ export class OpGroupingManager {
25
33
  }
26
34
 
27
35
  for (const message of batch.content) {
28
- // Blob attaches cannot be grouped (grouped batching would hide metadata)
29
- if (message.type === ContainerMessageType.BlobAttach) {
30
- return batch;
31
- }
32
36
  if (message.metadata) {
33
37
  const keys = Object.keys(message.metadata);
34
38
  assert(keys.length < 2, 0x5dd /* cannot group ops with metadata */);
@@ -64,11 +68,14 @@ export class OpGroupingManager {
64
68
  }
65
69
 
66
70
  public ungroupOp(op: ISequencedDocumentMessage): ISequencedDocumentMessage[] {
67
- if (op.contents?.type !== OpGroupingManager.groupedBatchOp) {
71
+ if (
72
+ (op.contents as { type?: unknown } | undefined)?.type !==
73
+ OpGroupingManager.groupedBatchOp
74
+ ) {
68
75
  return [op];
69
76
  }
70
77
 
71
- const messages = op.contents.contents as IGroupedMessage[];
78
+ const messages = (op.contents as IGroupedBatchMessageContents).contents;
72
79
  let fakeCsn = 1;
73
80
  return messages.map((subMessage) => ({
74
81
  ...op,
@@ -52,7 +52,9 @@ export class OpSplitter {
52
52
  };
53
53
  }
54
54
 
55
- const clientId = message.clientId;
55
+ // TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
56
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
57
+ const clientId = message.clientId as string;
56
58
  const chunkedContent = message.contents as IChunkedOp;
57
59
  this.addChunk(clientId, chunkedContent, message);
58
60