@fluidframework/container-runtime 2.31.1 → 2.33.0-333010

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 (301) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/container-runtime.test-files.tar +0 -0
  3. package/dist/channelCollection.d.ts +0 -14
  4. package/dist/channelCollection.d.ts.map +1 -1
  5. package/dist/channelCollection.js +10 -25
  6. package/dist/channelCollection.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +16 -9
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +137 -103
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/dataStoreContext.d.ts +1 -5
  12. package/dist/dataStoreContext.d.ts.map +1 -1
  13. package/dist/dataStoreContext.js +0 -8
  14. package/dist/dataStoreContext.js.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/messageTypes.d.ts +3 -7
  20. package/dist/messageTypes.d.ts.map +1 -1
  21. package/dist/messageTypes.js.map +1 -1
  22. package/dist/opLifecycle/batchManager.d.ts +3 -15
  23. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  24. package/dist/opLifecycle/batchManager.js +3 -40
  25. package/dist/opLifecycle/batchManager.js.map +1 -1
  26. package/dist/opLifecycle/definitions.d.ts +42 -6
  27. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  28. package/dist/opLifecycle/definitions.js.map +1 -1
  29. package/dist/opLifecycle/index.d.ts +5 -4
  30. package/dist/opLifecycle/index.d.ts.map +1 -1
  31. package/dist/opLifecycle/index.js +6 -4
  32. package/dist/opLifecycle/index.js.map +1 -1
  33. package/dist/opLifecycle/opCompressor.d.ts +10 -11
  34. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  35. package/dist/opLifecycle/opCompressor.js +21 -20
  36. package/dist/opLifecycle/opCompressor.js.map +1 -1
  37. package/dist/opLifecycle/opGroupingManager.d.ts +9 -5
  38. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  39. package/dist/opLifecycle/opGroupingManager.js +18 -22
  40. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  41. package/dist/opLifecycle/opSerialization.d.ts +20 -0
  42. package/dist/opLifecycle/opSerialization.d.ts.map +1 -0
  43. package/dist/opLifecycle/opSerialization.js +40 -0
  44. package/dist/opLifecycle/opSerialization.js.map +1 -0
  45. package/dist/opLifecycle/opSplitter.d.ts +12 -13
  46. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  47. package/dist/opLifecycle/opSplitter.js +15 -15
  48. package/dist/opLifecycle/opSplitter.js.map +1 -1
  49. package/dist/opLifecycle/outbox.d.ts +35 -18
  50. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  51. package/dist/opLifecycle/outbox.js +111 -72
  52. package/dist/opLifecycle/outbox.js.map +1 -1
  53. package/dist/opLifecycle/remoteMessageProcessor.d.ts +0 -7
  54. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  55. package/dist/opLifecycle/remoteMessageProcessor.js +1 -15
  56. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  57. package/dist/packageVersion.d.ts +1 -1
  58. package/dist/packageVersion.d.ts.map +1 -1
  59. package/dist/packageVersion.js +1 -1
  60. package/dist/packageVersion.js.map +1 -1
  61. package/dist/pendingStateManager.d.ts +2 -2
  62. package/dist/pendingStateManager.d.ts.map +1 -1
  63. package/dist/pendingStateManager.js +1 -1
  64. package/dist/pendingStateManager.js.map +1 -1
  65. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  66. package/dist/runtimeLayerCompatState.js +6 -5
  67. package/dist/runtimeLayerCompatState.js.map +1 -1
  68. package/dist/summary/index.d.ts +5 -8
  69. package/dist/summary/index.d.ts.map +1 -1
  70. package/dist/summary/index.js +20 -21
  71. package/dist/summary/index.js.map +1 -1
  72. package/dist/summary/orderedClientElection.js +9 -9
  73. package/dist/summary/orderedClientElection.js.map +1 -1
  74. package/dist/summary/summarizerClientElection.d.ts +0 -1
  75. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  76. package/dist/summary/summarizerClientElection.js +3 -3
  77. package/dist/summary/summarizerClientElection.js.map +1 -1
  78. package/dist/summary/summarizerTypes.d.ts +3 -75
  79. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  80. package/dist/summary/summarizerTypes.js +2 -0
  81. package/dist/summary/summarizerTypes.js.map +1 -1
  82. package/{lib/summary/summaryGenerator.d.ts → dist/summary/summarizerUtils.d.ts} +12 -43
  83. package/dist/summary/summarizerUtils.d.ts.map +1 -0
  84. package/dist/summary/summarizerUtils.js +71 -0
  85. package/dist/summary/summarizerUtils.js.map +1 -0
  86. package/dist/summary/summaryDelayLoadedModule/index.d.ts +10 -0
  87. package/dist/summary/summaryDelayLoadedModule/index.d.ts.map +1 -0
  88. package/dist/summary/summaryDelayLoadedModule/index.js +20 -0
  89. package/dist/summary/summaryDelayLoadedModule/index.js.map +1 -0
  90. package/dist/summary/{runWhileConnectedCoordinator.d.ts → summaryDelayLoadedModule/runWhileConnectedCoordinator.d.ts} +1 -1
  91. package/dist/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.d.ts.map +1 -0
  92. package/dist/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js.map +1 -0
  93. package/dist/summary/{runningSummarizer.d.ts → summaryDelayLoadedModule/runningSummarizer.d.ts} +4 -13
  94. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -0
  95. package/dist/summary/{runningSummarizer.js → summaryDelayLoadedModule/runningSummarizer.js} +17 -24
  96. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -0
  97. package/dist/summary/{summarizer.d.ts → summaryDelayLoadedModule/summarizer.d.ts} +13 -2
  98. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -0
  99. package/dist/summary/{summarizer.js → summaryDelayLoadedModule/summarizer.js} +13 -3
  100. package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -0
  101. package/{lib/summary → dist/summary/summaryDelayLoadedModule}/summarizerHeuristics.d.ts +3 -3
  102. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts.map +1 -0
  103. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -0
  104. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +36 -0
  105. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -0
  106. package/dist/summary/{summaryGenerator.js → summaryDelayLoadedModule/summaryGenerator.js} +14 -99
  107. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -0
  108. package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.d.ts +21 -0
  109. package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.d.ts.map +1 -0
  110. package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.js +44 -0
  111. package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.js.map +1 -0
  112. package/dist/summary/summaryDelayLoadedModule/summaryResultTypes.d.ts +80 -0
  113. package/dist/summary/summaryDelayLoadedModule/summaryResultTypes.d.ts.map +1 -0
  114. package/dist/summary/summaryDelayLoadedModule/summaryResultTypes.js +7 -0
  115. package/dist/summary/summaryDelayLoadedModule/summaryResultTypes.js.map +1 -0
  116. package/dist/summary/summaryHelpers.d.ts +1 -1
  117. package/dist/summary/summaryHelpers.d.ts.map +1 -1
  118. package/dist/summary/summaryHelpers.js +2 -2
  119. package/dist/summary/summaryHelpers.js.map +1 -1
  120. package/dist/summary/summaryManager.d.ts +4 -3
  121. package/dist/summary/summaryManager.d.ts.map +1 -1
  122. package/dist/summary/summaryManager.js +2 -2
  123. package/dist/summary/summaryManager.js.map +1 -1
  124. package/lib/channelCollection.d.ts +0 -14
  125. package/lib/channelCollection.d.ts.map +1 -1
  126. package/lib/channelCollection.js +6 -21
  127. package/lib/channelCollection.js.map +1 -1
  128. package/lib/containerRuntime.d.ts +16 -9
  129. package/lib/containerRuntime.d.ts.map +1 -1
  130. package/lib/containerRuntime.js +140 -106
  131. package/lib/containerRuntime.js.map +1 -1
  132. package/lib/dataStoreContext.d.ts +1 -5
  133. package/lib/dataStoreContext.d.ts.map +1 -1
  134. package/lib/dataStoreContext.js +0 -8
  135. package/lib/dataStoreContext.js.map +1 -1
  136. package/lib/index.d.ts +1 -1
  137. package/lib/index.d.ts.map +1 -1
  138. package/lib/index.js +1 -1
  139. package/lib/index.js.map +1 -1
  140. package/lib/messageTypes.d.ts +3 -7
  141. package/lib/messageTypes.d.ts.map +1 -1
  142. package/lib/messageTypes.js.map +1 -1
  143. package/lib/opLifecycle/batchManager.d.ts +3 -15
  144. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  145. package/lib/opLifecycle/batchManager.js +2 -38
  146. package/lib/opLifecycle/batchManager.js.map +1 -1
  147. package/lib/opLifecycle/definitions.d.ts +42 -6
  148. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  149. package/lib/opLifecycle/definitions.js.map +1 -1
  150. package/lib/opLifecycle/index.d.ts +5 -4
  151. package/lib/opLifecycle/index.d.ts.map +1 -1
  152. package/lib/opLifecycle/index.js +4 -3
  153. package/lib/opLifecycle/index.js.map +1 -1
  154. package/lib/opLifecycle/opCompressor.d.ts +10 -11
  155. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  156. package/lib/opLifecycle/opCompressor.js +21 -20
  157. package/lib/opLifecycle/opCompressor.js.map +1 -1
  158. package/lib/opLifecycle/opGroupingManager.d.ts +9 -5
  159. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  160. package/lib/opLifecycle/opGroupingManager.js +18 -22
  161. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  162. package/lib/opLifecycle/opSerialization.d.ts +20 -0
  163. package/lib/opLifecycle/opSerialization.d.ts.map +1 -0
  164. package/lib/opLifecycle/opSerialization.js +35 -0
  165. package/lib/opLifecycle/opSerialization.js.map +1 -0
  166. package/lib/opLifecycle/opSplitter.d.ts +12 -13
  167. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  168. package/lib/opLifecycle/opSplitter.js +14 -14
  169. package/lib/opLifecycle/opSplitter.js.map +1 -1
  170. package/lib/opLifecycle/outbox.d.ts +35 -18
  171. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  172. package/lib/opLifecycle/outbox.js +109 -71
  173. package/lib/opLifecycle/outbox.js.map +1 -1
  174. package/lib/opLifecycle/remoteMessageProcessor.d.ts +0 -7
  175. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  176. package/lib/opLifecycle/remoteMessageProcessor.js +0 -13
  177. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  178. package/lib/packageVersion.d.ts +1 -1
  179. package/lib/packageVersion.d.ts.map +1 -1
  180. package/lib/packageVersion.js +1 -1
  181. package/lib/packageVersion.js.map +1 -1
  182. package/lib/pendingStateManager.d.ts +2 -2
  183. package/lib/pendingStateManager.d.ts.map +1 -1
  184. package/lib/pendingStateManager.js +1 -1
  185. package/lib/pendingStateManager.js.map +1 -1
  186. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  187. package/lib/runtimeLayerCompatState.js +3 -2
  188. package/lib/runtimeLayerCompatState.js.map +1 -1
  189. package/lib/summary/index.d.ts +5 -8
  190. package/lib/summary/index.d.ts.map +1 -1
  191. package/lib/summary/index.js +5 -7
  192. package/lib/summary/index.js.map +1 -1
  193. package/lib/summary/orderedClientElection.js +1 -1
  194. package/lib/summary/orderedClientElection.js.map +1 -1
  195. package/lib/summary/summarizerClientElection.d.ts +0 -1
  196. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  197. package/lib/summary/summarizerClientElection.js +1 -1
  198. package/lib/summary/summarizerClientElection.js.map +1 -1
  199. package/lib/summary/summarizerTypes.d.ts +3 -75
  200. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  201. package/lib/summary/summarizerTypes.js +1 -1
  202. package/lib/summary/summarizerTypes.js.map +1 -1
  203. package/{dist/summary/summaryGenerator.d.ts → lib/summary/summarizerUtils.d.ts} +12 -43
  204. package/lib/summary/summarizerUtils.d.ts.map +1 -0
  205. package/lib/summary/summarizerUtils.js +64 -0
  206. package/lib/summary/summarizerUtils.js.map +1 -0
  207. package/lib/summary/summaryDelayLoadedModule/index.d.ts +10 -0
  208. package/lib/summary/summaryDelayLoadedModule/index.d.ts.map +1 -0
  209. package/lib/summary/summaryDelayLoadedModule/index.js +9 -0
  210. package/lib/summary/summaryDelayLoadedModule/index.js.map +1 -0
  211. package/lib/summary/{runWhileConnectedCoordinator.d.ts → summaryDelayLoadedModule/runWhileConnectedCoordinator.d.ts} +1 -1
  212. package/lib/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.d.ts.map +1 -0
  213. package/lib/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js.map +1 -0
  214. package/lib/summary/{runningSummarizer.d.ts → summaryDelayLoadedModule/runningSummarizer.d.ts} +4 -13
  215. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -0
  216. package/lib/summary/{runningSummarizer.js → summaryDelayLoadedModule/runningSummarizer.js} +5 -12
  217. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -0
  218. package/lib/summary/{summarizer.d.ts → summaryDelayLoadedModule/summarizer.d.ts} +13 -2
  219. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -0
  220. package/lib/summary/{summarizer.js → summaryDelayLoadedModule/summarizer.js} +11 -1
  221. package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -0
  222. package/{dist/summary → lib/summary/summaryDelayLoadedModule}/summarizerHeuristics.d.ts +3 -3
  223. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.d.ts.map +1 -0
  224. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -0
  225. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +36 -0
  226. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -0
  227. package/lib/summary/{summaryGenerator.js → summaryDelayLoadedModule/summaryGenerator.js} +4 -85
  228. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -0
  229. package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.d.ts +21 -0
  230. package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.d.ts.map +1 -0
  231. package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.js +40 -0
  232. package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.js.map +1 -0
  233. package/lib/summary/summaryDelayLoadedModule/summaryResultTypes.d.ts +80 -0
  234. package/lib/summary/summaryDelayLoadedModule/summaryResultTypes.d.ts.map +1 -0
  235. package/lib/summary/summaryDelayLoadedModule/summaryResultTypes.js +6 -0
  236. package/lib/summary/summaryDelayLoadedModule/summaryResultTypes.js.map +1 -0
  237. package/lib/summary/summaryHelpers.d.ts +1 -1
  238. package/lib/summary/summaryHelpers.d.ts.map +1 -1
  239. package/lib/summary/summaryHelpers.js +1 -1
  240. package/lib/summary/summaryHelpers.js.map +1 -1
  241. package/lib/summary/summaryManager.d.ts +4 -3
  242. package/lib/summary/summaryManager.d.ts.map +1 -1
  243. package/lib/summary/summaryManager.js +2 -2
  244. package/lib/summary/summaryManager.js.map +1 -1
  245. package/package.json +20 -19
  246. package/src/channelCollection.ts +5 -20
  247. package/src/containerRuntime.ts +220 -178
  248. package/src/dataStoreContext.ts +0 -11
  249. package/src/index.ts +0 -1
  250. package/src/messageTypes.ts +5 -19
  251. package/src/opLifecycle/batchManager.ts +12 -51
  252. package/src/opLifecycle/definitions.ts +49 -6
  253. package/src/opLifecycle/index.ts +19 -4
  254. package/src/opLifecycle/opCompressor.ts +26 -25
  255. package/src/opLifecycle/opGroupingManager.ts +27 -26
  256. package/src/opLifecycle/opSerialization.ts +46 -0
  257. package/src/opLifecycle/opSplitter.ts +21 -17
  258. package/src/opLifecycle/outbox.ts +168 -99
  259. package/src/opLifecycle/remoteMessageProcessor.ts +0 -17
  260. package/src/packageVersion.ts +1 -1
  261. package/src/pendingStateManager.ts +3 -3
  262. package/src/runtimeLayerCompatState.ts +3 -2
  263. package/src/summary/index.ts +35 -31
  264. package/src/summary/orderedClientElection.ts +1 -1
  265. package/src/summary/summarizerClientElection.ts +1 -2
  266. package/src/summary/summarizerTypes.ts +7 -91
  267. package/src/summary/summarizerUtils.ts +132 -0
  268. package/src/summary/summaryDelayLoadedModule/index.ts +28 -0
  269. package/src/summary/{runWhileConnectedCoordinator.ts → summaryDelayLoadedModule/runWhileConnectedCoordinator.ts} +1 -1
  270. package/src/summary/{runningSummarizer.ts → summaryDelayLoadedModule/runningSummarizer.ts} +13 -28
  271. package/src/summary/{summarizer.ts → summaryDelayLoadedModule/summarizer.ts} +19 -8
  272. package/src/summary/{summarizerHeuristics.ts → summaryDelayLoadedModule/summarizerHeuristics.ts} +3 -3
  273. package/src/summary/{summaryGenerator.ts → summaryDelayLoadedModule/summaryGenerator.ts} +13 -179
  274. package/src/summary/summaryDelayLoadedModule/summaryResultBuilder.ts +70 -0
  275. package/src/summary/summaryDelayLoadedModule/summaryResultTypes.ts +100 -0
  276. package/src/summary/summaryHelpers.ts +6 -6
  277. package/src/summary/summaryManager.ts +8 -6
  278. package/dist/summary/runWhileConnectedCoordinator.d.ts.map +0 -1
  279. package/dist/summary/runWhileConnectedCoordinator.js.map +0 -1
  280. package/dist/summary/runningSummarizer.d.ts.map +0 -1
  281. package/dist/summary/runningSummarizer.js.map +0 -1
  282. package/dist/summary/summarizer.d.ts.map +0 -1
  283. package/dist/summary/summarizer.js.map +0 -1
  284. package/dist/summary/summarizerHeuristics.d.ts.map +0 -1
  285. package/dist/summary/summarizerHeuristics.js.map +0 -1
  286. package/dist/summary/summaryGenerator.d.ts.map +0 -1
  287. package/dist/summary/summaryGenerator.js.map +0 -1
  288. package/lib/summary/runWhileConnectedCoordinator.d.ts.map +0 -1
  289. package/lib/summary/runWhileConnectedCoordinator.js.map +0 -1
  290. package/lib/summary/runningSummarizer.d.ts.map +0 -1
  291. package/lib/summary/runningSummarizer.js.map +0 -1
  292. package/lib/summary/summarizer.d.ts.map +0 -1
  293. package/lib/summary/summarizer.js.map +0 -1
  294. package/lib/summary/summarizerHeuristics.d.ts.map +0 -1
  295. package/lib/summary/summarizerHeuristics.js.map +0 -1
  296. package/lib/summary/summaryGenerator.d.ts.map +0 -1
  297. package/lib/summary/summaryGenerator.js.map +0 -1
  298. /package/dist/summary/{runWhileConnectedCoordinator.js → summaryDelayLoadedModule/runWhileConnectedCoordinator.js} +0 -0
  299. /package/dist/summary/{summarizerHeuristics.js → summaryDelayLoadedModule/summarizerHeuristics.js} +0 -0
  300. /package/lib/summary/{runWhileConnectedCoordinator.js → summaryDelayLoadedModule/runWhileConnectedCoordinator.js} +0 -0
  301. /package/lib/summary/{summarizerHeuristics.js → summaryDelayLoadedModule/summarizerHeuristics.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"opCompressor.js","sourceRoot":"","sources":["../../src/opLifecycle/opCompressor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IsoBuffer } from \"@fluid-internal/client-utils\";\nimport { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tUsageError,\n\tcreateChildLogger,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { compress } from \"lz4js\";\n\nimport { CompressionAlgorithms } from \"../containerRuntime.js\";\n\nimport { estimateSocketSize } from \"./batchManager.js\";\nimport { BatchMessage, IBatch } from \"./definitions.js\";\n\n/**\n * Compresses batches of ops. It generates a single compressed op that contains\n * the contents of each op in the batch. It then submits empty ops for each original\n * op to reserve sequence numbers.\n */\nexport class OpCompressor {\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(logger: ITelemetryBaseLogger) {\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpCompressor\" });\n\t}\n\n\t/**\n\t * Combines the contents of the batch into a single JSON string and compresses it, putting\n\t * the resulting string as the first message of the batch. The rest of the messages are\n\t * empty placeholders to reserve sequence numbers.\n\t * This should only take a single message batch and compress it.\n\t * @param batch - The batch to compress\n\t * @returns A batch of the same length as the input batch, containing a single compressed message followed by empty placeholders\n\t */\n\tpublic compressBatch(batch: IBatch): IBatch<[BatchMessage]> {\n\t\tassert(\n\t\t\tbatch.contentSizeInBytes > 0 && batch.messages.length === 1,\n\t\t\t0x5a4 /* Batch should not be empty and should contain a single message */,\n\t\t);\n\n\t\tconst compressionStart = Date.now();\n\t\tconst contentsAsBuffer = new TextEncoder().encode(this.serializeBatchContents(batch));\n\t\tconst compressedContents = compress(contentsAsBuffer);\n\t\tconst compressedContent = IsoBuffer.from(compressedContents).toString(\"base64\");\n\t\tconst duration = Date.now() - compressionStart;\n\n\t\tconst messages: [BatchMessage] = [\n\t\t\t{\n\t\t\t\t...batch.messages[0],\n\t\t\t\tcontents: JSON.stringify({ packedContents: compressedContent }),\n\t\t\t\tmetadata: batch.messages[0].metadata,\n\t\t\t\tcompression: CompressionAlgorithms.lz4,\n\t\t\t},\n\t\t];\n\n\t\tconst compressedBatch = {\n\t\t\tcontentSizeInBytes: compressedContent.length,\n\t\t\tmessages,\n\t\t\treferenceSequenceNumber: batch.referenceSequenceNumber,\n\t\t};\n\n\t\tif (batch.contentSizeInBytes > 200000) {\n\t\t\tthis.logger.sendPerformanceEvent({\n\t\t\t\teventName: \"CompressedBatch\",\n\t\t\t\tduration,\n\t\t\t\tsizeBeforeCompression: batch.contentSizeInBytes,\n\t\t\t\tsizeAfterCompression: compressedBatch.contentSizeInBytes,\n\t\t\t\topCount: compressedBatch.messages.length,\n\t\t\t\tsocketSize: estimateSocketSize(compressedBatch),\n\t\t\t});\n\t\t}\n\n\t\treturn compressedBatch;\n\t}\n\n\t/**\n\t * Combine the batch's content strings into a single JSON string (a serialized array)\n\t */\n\tprivate serializeBatchContents(batch: IBatch): string {\n\t\ttry {\n\t\t\t// Yields a valid JSON array, since each message.contents is already serialized to JSON\n\t\t\treturn `[${batch.messages.map(({ contents }) => contents).join(\",\")}]`;\n\t\t} catch (newError: unknown) {\n\t\t\tif ((newError as Partial<Error>).message === \"Invalid string length\") {\n\t\t\t\t// This is how JSON.stringify signals that\n\t\t\t\t// the content size exceeds its capacity\n\t\t\t\tconst error = new UsageError(\"Payload too large\");\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: \"BatchTooLarge\",\n\t\t\t\t\t\tsize: batch.contentSizeInBytes,\n\t\t\t\t\t\tlength: batch.messages.length,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tthrow newError;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"opCompressor.js","sourceRoot":"","sources":["../../src/opLifecycle/opCompressor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IsoBuffer } from \"@fluid-internal/client-utils\";\nimport { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tDataProcessingError,\n\tcreateChildLogger,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport { compress } from \"lz4js\";\n\nimport { CompressionAlgorithms } from \"../containerRuntime.js\";\n\nimport { type OutboundBatchMessage, type OutboundSingletonBatch } from \"./definitions.js\";\nimport { estimateSocketSize } from \"./outbox.js\";\n\n/**\n * Compresses batches of ops.\n *\n * @remarks Only single-message batches are supported\n * Use opGroupingManager to group a batch into a singleton batch suitable for compression.\n */\nexport class OpCompressor {\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(logger: ITelemetryBaseLogger) {\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpCompressor\" });\n\t}\n\n\t/**\n\t * Combines the contents of the singleton batch into a single JSON string and compresses it, putting\n\t * the resulting string as the message contents in place of the original uncompressed payload.\n\t * @param batch - The batch to compress. Must have only 1 message\n\t * @returns A singleton batch containing a single compressed message\n\t */\n\tpublic compressBatch(batch: OutboundSingletonBatch): OutboundSingletonBatch {\n\t\tassert(\n\t\t\tbatch.contentSizeInBytes > 0 && batch.messages.length === 1,\n\t\t\t0x5a4 /* Batch should not be empty and should contain a single message */,\n\t\t);\n\n\t\tconst compressionStart = Date.now();\n\t\tconst contentsAsBuffer = new TextEncoder().encode(this.serializeBatchContents(batch));\n\t\tconst compressedContents = compress(contentsAsBuffer);\n\t\tconst compressedContent = IsoBuffer.from(compressedContents).toString(\"base64\");\n\t\tconst duration = Date.now() - compressionStart;\n\n\t\tconst messages: [OutboundBatchMessage] = [\n\t\t\t{\n\t\t\t\t...batch.messages[0],\n\t\t\t\tcontents: JSON.stringify({ packedContents: compressedContent }),\n\t\t\t\tmetadata: batch.messages[0].metadata,\n\t\t\t\tcompression: CompressionAlgorithms.lz4,\n\t\t\t},\n\t\t];\n\n\t\tconst compressedBatch: OutboundSingletonBatch = {\n\t\t\tcontentSizeInBytes: compressedContent.length,\n\t\t\tmessages,\n\t\t\treferenceSequenceNumber: batch.referenceSequenceNumber,\n\t\t};\n\n\t\tif (batch.contentSizeInBytes > 200000) {\n\t\t\tthis.logger.sendPerformanceEvent({\n\t\t\t\teventName: \"CompressedBatch\",\n\t\t\t\tduration,\n\t\t\t\tsizeBeforeCompression: batch.contentSizeInBytes,\n\t\t\t\tsizeAfterCompression: compressedBatch.contentSizeInBytes,\n\t\t\t\topCount: compressedBatch.messages.length,\n\t\t\t\tsocketSize: estimateSocketSize(compressedBatch),\n\t\t\t});\n\t\t}\n\n\t\treturn compressedBatch;\n\t}\n\n\t/**\n\t * Combine the batch's content strings into a single JSON string (a serialized array)\n\t */\n\tprivate serializeBatchContents(batch: OutboundSingletonBatch): string {\n\t\tconst [message, ...none] = batch.messages;\n\t\tassert(none.length === 0, 0xb78 /* Batch should only contain a single message */);\n\t\ttry {\n\t\t\t// This is expressed as a JSON array, for legacy reasons\n\t\t\treturn `[${message.contents}]`;\n\t\t} catch (error: unknown) {\n\t\t\tif ((error as Partial<Error>).message === \"Invalid string length\") {\n\t\t\t\t// This is how string interpolation signals that\n\t\t\t\t// the content size exceeds its capacity\n\t\t\t\tconst dpe = DataProcessingError.create(\"Payload too large\", \"OpCompressor\");\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: \"BatchTooLarge\",\n\t\t\t\t\t\tsize: batch.contentSizeInBytes,\n\t\t\t\t\t\tlength: batch.messages.length,\n\t\t\t\t\t},\n\t\t\t\t\tdpe,\n\t\t\t\t);\n\t\t\t\tthrow dpe;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"]}
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
6
6
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
7
- import { IBatch, type BatchMessage } from "./definitions.js";
7
+ import { type OutboundBatch, type OutboundBatchMessage, type OutboundSingletonBatch } from "./definitions.js";
8
8
  export declare function isGroupedBatch(op: ISequencedDocumentMessage): boolean;
9
9
  export interface OpGroupingManagerConfig {
10
10
  readonly groupedBatchingEnabled: boolean;
@@ -19,19 +19,23 @@ export declare class OpGroupingManager {
19
19
  * This is needed as a placeholder if a batch becomes empty on resubmit, but we are tracking batch IDs.
20
20
  * @param resubmittingBatchId - batch ID of the resubmitting batch
21
21
  * @param referenceSequenceNumber - reference sequence number
22
- * @returns - IBatch containing a single empty Grouped Batch op
22
+ * @returns - The outbound batch as well as the interior placeholder message
23
23
  */
24
- createEmptyGroupedBatch(resubmittingBatchId: string, referenceSequenceNumber: number): IBatch<[BatchMessage]>;
24
+ createEmptyGroupedBatch(resubmittingBatchId: string, referenceSequenceNumber: number): {
25
+ outboundBatch: OutboundSingletonBatch;
26
+ placeholderMessage: OutboundBatchMessage;
27
+ };
25
28
  /**
26
29
  * Converts the given batch into a "grouped batch" - a batch with a single message of type "groupedBatch",
27
30
  * with contents being an array of the original batch's messages.
28
31
  *
32
+ * If the batch already has only 1 message, it is returned as-is.
33
+ *
29
34
  * @remarks - Remember that a BatchMessage has its content JSON serialized, so the incoming batch message contents
30
35
  * must be parsed first, and then the type and contents mentioned above are hidden in that JSON serialization.
31
36
  */
32
- groupBatch(batch: IBatch): IBatch<[BatchMessage]>;
37
+ groupBatch(batch: OutboundBatch): OutboundSingletonBatch;
33
38
  ungroupOp(op: ISequencedDocumentMessage): ISequencedDocumentMessage[];
34
- shouldGroup(batch: IBatch): boolean;
35
39
  groupedBatchingEnabled(): boolean;
36
40
  }
37
41
  //# sourceMappingURL=opGroupingManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"opGroupingManager.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opGroupingManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAEvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAMxF,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAuB7D,wBAAgB,cAAc,CAAC,EAAE,EAAE,yBAAyB,GAAG,OAAO,CAErE;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CACzC;AAED,qBAAa,iBAAiB;IAK5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJxB,MAAM,CAAC,QAAQ,CAAC,cAAc,kBAAkB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAG3B,MAAM,EAAE,uBAAuB,EAChD,MAAM,EAAE,oBAAoB;IAK7B;;;;;;OAMG;IACI,uBAAuB,CAC7B,mBAAmB,EAAE,MAAM,EAC3B,uBAAuB,EAAE,MAAM,GAC7B,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAwBzB;;;;;;OAMG;IACI,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IA6CjD,SAAS,CAAC,EAAE,EAAE,yBAAyB,GAAG,yBAAyB,EAAE;IAcrE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAUnC,sBAAsB,IAAI,OAAO;CAGxC"}
1
+ {"version":3,"file":"opGroupingManager.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opGroupingManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAEvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAMxF,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,MAAM,kBAAkB,CAAC;AAuB1B,wBAAgB,cAAc,CAAC,EAAE,EAAE,yBAAyB,GAAG,OAAO,CAErE;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CACzC;AAED,qBAAa,iBAAiB;IAK5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJxB,MAAM,CAAC,QAAQ,CAAC,cAAc,kBAAkB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAG3B,MAAM,EAAE,uBAAuB,EAChD,MAAM,EAAE,oBAAoB;IAK7B;;;;;;OAMG;IACI,uBAAuB,CAC7B,mBAAmB,EAAE,MAAM,EAC3B,uBAAuB,EAAE,MAAM,GAC7B;QAAE,aAAa,EAAE,sBAAsB,CAAC;QAAC,kBAAkB,EAAE,oBAAoB,CAAA;KAAE;IAwBtF;;;;;;;;OAQG;IACI,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,sBAAsB;IAkDxD,SAAS,CAAC,EAAE,EAAE,yBAAyB,GAAG,yBAAyB,EAAE;IAcrE,sBAAsB,IAAI,OAAO;CAGxC"}
@@ -25,36 +25,42 @@ class OpGroupingManager {
25
25
  * This is needed as a placeholder if a batch becomes empty on resubmit, but we are tracking batch IDs.
26
26
  * @param resubmittingBatchId - batch ID of the resubmitting batch
27
27
  * @param referenceSequenceNumber - reference sequence number
28
- * @returns - IBatch containing a single empty Grouped Batch op
28
+ * @returns - The outbound batch as well as the interior placeholder message
29
29
  */
30
30
  createEmptyGroupedBatch(resubmittingBatchId, referenceSequenceNumber) {
31
31
  (0, internal_1.assert)(this.config.groupedBatchingEnabled, 0xa00 /* cannot create empty grouped batch when grouped batching is disabled */);
32
- const serializedContent = JSON.stringify({
32
+ const serializedOp = JSON.stringify({
33
33
  type: OpGroupingManager.groupedBatchOp,
34
34
  contents: [],
35
35
  });
36
- return {
36
+ const placeholderMessage = {
37
+ metadata: { batchId: resubmittingBatchId },
38
+ localOpMetadata: { emptyBatch: true },
39
+ referenceSequenceNumber,
40
+ contents: serializedOp,
41
+ };
42
+ const outboundBatch = {
37
43
  contentSizeInBytes: 0,
38
- messages: [
39
- {
40
- metadata: { batchId: resubmittingBatchId },
41
- localOpMetadata: { emptyBatch: true },
42
- referenceSequenceNumber,
43
- contents: serializedContent,
44
- },
45
- ],
44
+ messages: [placeholderMessage],
46
45
  referenceSequenceNumber,
47
46
  };
47
+ return { outboundBatch, placeholderMessage };
48
48
  }
49
49
  /**
50
50
  * Converts the given batch into a "grouped batch" - a batch with a single message of type "groupedBatch",
51
51
  * with contents being an array of the original batch's messages.
52
52
  *
53
+ * If the batch already has only 1 message, it is returned as-is.
54
+ *
53
55
  * @remarks - Remember that a BatchMessage has its content JSON serialized, so the incoming batch message contents
54
56
  * must be parsed first, and then the type and contents mentioned above are hidden in that JSON serialization.
55
57
  */
56
58
  groupBatch(batch) {
57
- (0, internal_1.assert)(this.shouldGroup(batch), 0x946 /* cannot group the provided batch */);
59
+ (0, internal_1.assert)(this.groupedBatchingEnabled(), 0xb79 /* grouping disabled! */);
60
+ (0, internal_1.assert)(batch.messages.length > 0, 0xb7a /* Unexpected attempt to group an empty batch */);
61
+ if (batch.messages.length === 1) {
62
+ return batch;
63
+ }
58
64
  if (batch.messages.length >= 1000) {
59
65
  this.logger.sendTelemetryEvent({
60
66
  eventName: "GroupLargeBatch",
@@ -106,16 +112,6 @@ class OpGroupingManager {
106
112
  compression: subMessage.compression,
107
113
  }));
108
114
  }
109
- shouldGroup(batch) {
110
- return (
111
- // Grouped batching must be enabled
112
- this.config.groupedBatchingEnabled &&
113
- // The number of ops in the batch must be 2 or more
114
- // or be empty (to allow for empty batches to be grouped)
115
- batch.messages.length !== 1
116
- // Support for reentrant batches will be on by default
117
- );
118
- }
119
115
  groupedBatchingEnabled() {
120
116
  return this.config.groupedBatchingEnabled;
121
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"opGroupingManager.js","sourceRoot":"","sources":["../../src/opLifecycle/opGroupingManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tcreateChildLogger,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { IBatch, type BatchMessage } from \"./definitions.js\";\n\n/**\n * Grouping makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.\n */\ninterface IGroupedBatchMessageContents {\n\ttype: typeof OpGroupingManager.groupedBatchOp;\n\tcontents: IGroupedMessage[];\n}\n\ninterface IGroupedMessage {\n\tcontents?: unknown;\n\tmetadata?: Record<string, unknown>;\n\tcompression?: string;\n}\n\nfunction isGroupContents(opContents: unknown): opContents is IGroupedBatchMessageContents {\n\treturn (\n\t\t(opContents as Partial<IGroupedBatchMessageContents>)?.type ===\n\t\tOpGroupingManager.groupedBatchOp\n\t);\n}\n\nexport function isGroupedBatch(op: ISequencedDocumentMessage): boolean {\n\treturn isGroupContents(op.contents);\n}\n\nexport interface OpGroupingManagerConfig {\n\treadonly groupedBatchingEnabled: boolean;\n}\n\nexport class OpGroupingManager {\n\tstatic readonly groupedBatchOp = \"groupedBatch\";\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(\n\t\tprivate readonly config: OpGroupingManagerConfig,\n\t\tlogger: ITelemetryBaseLogger,\n\t) {\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpGroupingManager\" });\n\t}\n\n\t/**\n\t * Creates a new batch with a single message of type \"groupedBatch\" and empty contents.\n\t * This is needed as a placeholder if a batch becomes empty on resubmit, but we are tracking batch IDs.\n\t * @param resubmittingBatchId - batch ID of the resubmitting batch\n\t * @param referenceSequenceNumber - reference sequence number\n\t * @returns - IBatch containing a single empty Grouped Batch op\n\t */\n\tpublic createEmptyGroupedBatch(\n\t\tresubmittingBatchId: string,\n\t\treferenceSequenceNumber: number,\n\t): IBatch<[BatchMessage]> {\n\t\tassert(\n\t\t\tthis.config.groupedBatchingEnabled,\n\t\t\t0xa00 /* cannot create empty grouped batch when grouped batching is disabled */,\n\t\t);\n\t\tconst serializedContent = JSON.stringify({\n\t\t\ttype: OpGroupingManager.groupedBatchOp,\n\t\t\tcontents: [],\n\t\t});\n\n\t\treturn {\n\t\t\tcontentSizeInBytes: 0,\n\t\t\tmessages: [\n\t\t\t\t{\n\t\t\t\t\tmetadata: { batchId: resubmittingBatchId },\n\t\t\t\t\tlocalOpMetadata: { emptyBatch: true },\n\t\t\t\t\treferenceSequenceNumber,\n\t\t\t\t\tcontents: serializedContent,\n\t\t\t\t},\n\t\t\t],\n\t\t\treferenceSequenceNumber,\n\t\t};\n\t}\n\n\t/**\n\t * Converts the given batch into a \"grouped batch\" - a batch with a single message of type \"groupedBatch\",\n\t * with contents being an array of the original batch's messages.\n\t *\n\t * @remarks - Remember that a BatchMessage has its content JSON serialized, so the incoming batch message contents\n\t * must be parsed first, and then the type and contents mentioned above are hidden in that JSON serialization.\n\t */\n\tpublic groupBatch(batch: IBatch): IBatch<[BatchMessage]> {\n\t\tassert(this.shouldGroup(batch), 0x946 /* cannot group the provided batch */);\n\n\t\tif (batch.messages.length >= 1000) {\n\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"GroupLargeBatch\",\n\t\t\t\tlength: batch.messages.length,\n\t\t\t\treentrant: batch.hasReentrantOps,\n\t\t\t\treferenceSequenceNumber: batch.messages[0].referenceSequenceNumber,\n\t\t\t});\n\t\t}\n\t\t// We expect this will be on the first message, if present at all.\n\t\tlet groupedBatchId;\n\t\tfor (const message of batch.messages) {\n\t\t\tif (message.metadata) {\n\t\t\t\tconst { batch: _batch, batchId, ...rest } = message.metadata;\n\t\t\t\tif (batchId) {\n\t\t\t\t\tgroupedBatchId = batchId;\n\t\t\t\t}\n\t\t\t\tassert(Object.keys(rest).length === 0, 0x5dd /* cannot group ops with metadata */);\n\t\t\t}\n\t\t}\n\n\t\tconst serializedContent = JSON.stringify({\n\t\t\ttype: OpGroupingManager.groupedBatchOp,\n\t\t\tcontents: batch.messages.map<IGroupedMessage>((message) => ({\n\t\t\t\tcontents: message.contents === undefined ? undefined : JSON.parse(message.contents),\n\t\t\t\tmetadata: message.metadata,\n\t\t\t\tcompression: message.compression,\n\t\t\t})),\n\t\t});\n\n\t\tconst groupedBatch: IBatch<[BatchMessage]> = {\n\t\t\t...batch,\n\t\t\tmessages: [\n\t\t\t\t{\n\t\t\t\t\tmetadata: { batchId: groupedBatchId },\n\t\t\t\t\treferenceSequenceNumber: batch.messages[0].referenceSequenceNumber,\n\t\t\t\t\tcontents: serializedContent,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t\treturn groupedBatch;\n\t}\n\n\tpublic ungroupOp(op: ISequencedDocumentMessage): ISequencedDocumentMessage[] {\n\t\tassert(isGroupContents(op.contents), 0x947 /* can only ungroup a grouped batch */);\n\t\tconst contents: IGroupedBatchMessageContents = op.contents;\n\n\t\tlet fakeCsn = 1;\n\t\treturn contents.contents.map((subMessage) => ({\n\t\t\t...op,\n\t\t\tclientSequenceNumber: fakeCsn++,\n\t\t\tcontents: subMessage.contents,\n\t\t\tmetadata: subMessage.metadata,\n\t\t\tcompression: subMessage.compression,\n\t\t}));\n\t}\n\n\tpublic shouldGroup(batch: IBatch): boolean {\n\t\treturn (\n\t\t\t// Grouped batching must be enabled\n\t\t\tthis.config.groupedBatchingEnabled &&\n\t\t\t// The number of ops in the batch must be 2 or more\n\t\t\t// or be empty (to allow for empty batches to be grouped)\n\t\t\tbatch.messages.length !== 1\n\t\t\t// Support for reentrant batches will be on by default\n\t\t);\n\t}\n\tpublic groupedBatchingEnabled(): boolean {\n\t\treturn this.config.groupedBatchingEnabled;\n\t}\n}\n"]}
1
+ {"version":3,"file":"opGroupingManager.js","sourceRoot":"","sources":["../../src/opLifecycle/opGroupingManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tcreateChildLogger,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport {\n\ttype OutboundBatch,\n\ttype OutboundBatchMessage,\n\ttype OutboundSingletonBatch,\n} from \"./definitions.js\";\n\n/**\n * Grouping makes assumptions about the shape of message contents. This interface codifies those assumptions, but does not validate them.\n */\ninterface IGroupedBatchMessageContents {\n\ttype: typeof OpGroupingManager.groupedBatchOp;\n\tcontents: IGroupedMessage[];\n}\n\ninterface IGroupedMessage {\n\tcontents?: unknown;\n\tmetadata?: Record<string, unknown>;\n\tcompression?: string;\n}\n\nfunction isGroupContents(opContents: unknown): opContents is IGroupedBatchMessageContents {\n\treturn (\n\t\t(opContents as Partial<IGroupedBatchMessageContents>)?.type ===\n\t\tOpGroupingManager.groupedBatchOp\n\t);\n}\n\nexport function isGroupedBatch(op: ISequencedDocumentMessage): boolean {\n\treturn isGroupContents(op.contents);\n}\n\nexport interface OpGroupingManagerConfig {\n\treadonly groupedBatchingEnabled: boolean;\n}\n\nexport class OpGroupingManager {\n\tstatic readonly groupedBatchOp = \"groupedBatch\";\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(\n\t\tprivate readonly config: OpGroupingManagerConfig,\n\t\tlogger: ITelemetryBaseLogger,\n\t) {\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpGroupingManager\" });\n\t}\n\n\t/**\n\t * Creates a new batch with a single message of type \"groupedBatch\" and empty contents.\n\t * This is needed as a placeholder if a batch becomes empty on resubmit, but we are tracking batch IDs.\n\t * @param resubmittingBatchId - batch ID of the resubmitting batch\n\t * @param referenceSequenceNumber - reference sequence number\n\t * @returns - The outbound batch as well as the interior placeholder message\n\t */\n\tpublic createEmptyGroupedBatch(\n\t\tresubmittingBatchId: string,\n\t\treferenceSequenceNumber: number,\n\t): { outboundBatch: OutboundSingletonBatch; placeholderMessage: OutboundBatchMessage } {\n\t\tassert(\n\t\t\tthis.config.groupedBatchingEnabled,\n\t\t\t0xa00 /* cannot create empty grouped batch when grouped batching is disabled */,\n\t\t);\n\t\tconst serializedOp = JSON.stringify({\n\t\t\ttype: OpGroupingManager.groupedBatchOp,\n\t\t\tcontents: [],\n\t\t});\n\n\t\tconst placeholderMessage: OutboundBatchMessage = {\n\t\t\tmetadata: { batchId: resubmittingBatchId },\n\t\t\tlocalOpMetadata: { emptyBatch: true },\n\t\t\treferenceSequenceNumber,\n\t\t\tcontents: serializedOp,\n\t\t};\n\t\tconst outboundBatch: OutboundSingletonBatch = {\n\t\t\tcontentSizeInBytes: 0,\n\t\t\tmessages: [placeholderMessage],\n\t\t\treferenceSequenceNumber,\n\t\t};\n\t\treturn { outboundBatch, placeholderMessage };\n\t}\n\n\t/**\n\t * Converts the given batch into a \"grouped batch\" - a batch with a single message of type \"groupedBatch\",\n\t * with contents being an array of the original batch's messages.\n\t *\n\t * If the batch already has only 1 message, it is returned as-is.\n\t *\n\t * @remarks - Remember that a BatchMessage has its content JSON serialized, so the incoming batch message contents\n\t * must be parsed first, and then the type and contents mentioned above are hidden in that JSON serialization.\n\t */\n\tpublic groupBatch(batch: OutboundBatch): OutboundSingletonBatch {\n\t\tassert(this.groupedBatchingEnabled(), 0xb79 /* grouping disabled! */);\n\t\tassert(batch.messages.length > 0, 0xb7a /* Unexpected attempt to group an empty batch */);\n\n\t\tif (batch.messages.length === 1) {\n\t\t\treturn batch as OutboundSingletonBatch;\n\t\t}\n\n\t\tif (batch.messages.length >= 1000) {\n\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"GroupLargeBatch\",\n\t\t\t\tlength: batch.messages.length,\n\t\t\t\treentrant: batch.hasReentrantOps,\n\t\t\t\treferenceSequenceNumber: batch.messages[0].referenceSequenceNumber,\n\t\t\t});\n\t\t}\n\t\t// We expect this will be on the first message, if present at all.\n\t\tlet groupedBatchId;\n\t\tfor (const message of batch.messages) {\n\t\t\tif (message.metadata) {\n\t\t\t\tconst { batch: _batch, batchId, ...rest } = message.metadata;\n\t\t\t\tif (batchId) {\n\t\t\t\t\tgroupedBatchId = batchId;\n\t\t\t\t}\n\t\t\t\tassert(Object.keys(rest).length === 0, 0x5dd /* cannot group ops with metadata */);\n\t\t\t}\n\t\t}\n\n\t\tconst serializedContent = JSON.stringify({\n\t\t\ttype: OpGroupingManager.groupedBatchOp,\n\t\t\tcontents: batch.messages.map<IGroupedMessage>((message) => ({\n\t\t\t\tcontents: message.contents === undefined ? undefined : JSON.parse(message.contents),\n\t\t\t\tmetadata: message.metadata,\n\t\t\t\tcompression: message.compression,\n\t\t\t})),\n\t\t});\n\n\t\tconst groupedBatch: OutboundSingletonBatch = {\n\t\t\t...batch,\n\t\t\tmessages: [\n\t\t\t\t{\n\t\t\t\t\tmetadata: { batchId: groupedBatchId },\n\t\t\t\t\treferenceSequenceNumber: batch.messages[0].referenceSequenceNumber,\n\t\t\t\t\tcontents: serializedContent,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t\treturn groupedBatch;\n\t}\n\n\tpublic ungroupOp(op: ISequencedDocumentMessage): ISequencedDocumentMessage[] {\n\t\tassert(isGroupContents(op.contents), 0x947 /* can only ungroup a grouped batch */);\n\t\tconst contents: IGroupedBatchMessageContents = op.contents;\n\n\t\tlet fakeCsn = 1;\n\t\treturn contents.contents.map((subMessage) => ({\n\t\t\t...op,\n\t\t\tclientSequenceNumber: fakeCsn++,\n\t\t\tcontents: subMessage.contents,\n\t\t\tmetadata: subMessage.metadata,\n\t\t\tcompression: subMessage.compression,\n\t\t}));\n\t}\n\n\tpublic groupedBatchingEnabled(): boolean {\n\t\treturn this.config.groupedBatchingEnabled;\n\t}\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import type { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
6
+ import type { LocalContainerRuntimeMessage } from "../messageTypes.js";
7
+ /**
8
+ * Takes an incoming runtime message (outer type "op"), JSON.parses the message's contents in place,
9
+ * if needed (old Loader does this for us).
10
+ * Only to be used for runtime messages. The contents here would be the virtualized payload for a batch of ops.
11
+ * @remarks - Serialization during submit happens via {@link serializeOp}
12
+ * @param mutableMessage - op message received
13
+ */
14
+ export declare function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void;
15
+ /**
16
+ * Before submitting an op to the Outbox, its contents must be serialized using this function.
17
+ * @remarks - The deserialization on process happens via the function {@link ensureContentsDeserialized}.
18
+ */
19
+ export declare function serializeOp(op: LocalContainerRuntimeMessage): string;
20
+ //# sourceMappingURL=opSerialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opSerialization.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opSerialization.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAO7F,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAEvE;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,yBAAyB,GAAG,IAAI,CAM1F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,4BAA4B,GAAG,MAAM,CAYpE"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.serializeOp = exports.ensureContentsDeserialized = void 0;
8
+ const internal_1 = require("@fluidframework/runtime-utils/internal");
9
+ /**
10
+ * Takes an incoming runtime message (outer type "op"), JSON.parses the message's contents in place,
11
+ * if needed (old Loader does this for us).
12
+ * Only to be used for runtime messages. The contents here would be the virtualized payload for a batch of ops.
13
+ * @remarks - Serialization during submit happens via {@link serializeOp}
14
+ * @param mutableMessage - op message received
15
+ */
16
+ function ensureContentsDeserialized(mutableMessage) {
17
+ // This should become unconditional once Loader LTS reaches 2.4 or later.
18
+ // There will be a long time of needing both cases, until LTS advances to that point.
19
+ if (typeof mutableMessage.contents === "string" && mutableMessage.contents !== "") {
20
+ mutableMessage.contents = JSON.parse(mutableMessage.contents);
21
+ }
22
+ }
23
+ exports.ensureContentsDeserialized = ensureContentsDeserialized;
24
+ /**
25
+ * Before submitting an op to the Outbox, its contents must be serialized using this function.
26
+ * @remarks - The deserialization on process happens via the function {@link ensureContentsDeserialized}.
27
+ */
28
+ function serializeOp(op) {
29
+ return JSON.stringify(op,
30
+ // replacer:
31
+ (key, value) => {
32
+ // If 'value' is an IFluidHandle return its encoded form.
33
+ if ((0, internal_1.isFluidHandle)(value)) {
34
+ return (0, internal_1.encodeHandleForSerialization)((0, internal_1.toFluidHandleInternal)(value));
35
+ }
36
+ return value;
37
+ });
38
+ }
39
+ exports.serializeOp = serializeOp;
40
+ //# sourceMappingURL=opSerialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opSerialization.js","sourceRoot":"","sources":["../../src/opLifecycle/opSerialization.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAGH,qEAIgD;AAIhD;;;;;;GAMG;AACH,SAAgB,0BAA0B,CAAC,cAAyC;IACnF,yEAAyE;IACzE,qFAAqF;IACrF,IAAI,OAAO,cAAc,CAAC,QAAQ,KAAK,QAAQ,IAAI,cAAc,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QACnF,cAAc,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC/D,CAAC;AACF,CAAC;AAND,gEAMC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,EAAgC;IAC3D,OAAO,IAAI,CAAC,SAAS,CACpB,EAAE;IACF,YAAY;IACZ,CAAC,GAAG,EAAE,KAAc,EAAE,EAAE;QACvB,yDAAyD;QACzD,IAAI,IAAA,wBAAa,EAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,IAAA,uCAA4B,EAAC,IAAA,gCAAqB,EAAC,KAAK,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC,CACD,CAAC;AACH,CAAC;AAZD,kCAYC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tencodeHandleForSerialization,\n\tisFluidHandle,\n\ttoFluidHandleInternal,\n} from \"@fluidframework/runtime-utils/internal\";\n\nimport type { LocalContainerRuntimeMessage } from \"../messageTypes.js\";\n\n/**\n * Takes an incoming runtime message (outer type \"op\"), JSON.parses the message's contents in place,\n * if needed (old Loader does this for us).\n * Only to be used for runtime messages. The contents here would be the virtualized payload for a batch of ops.\n * @remarks - Serialization during submit happens via {@link serializeOp}\n * @param mutableMessage - op message received\n */\nexport function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void {\n\t// This should become unconditional once Loader LTS reaches 2.4 or later.\n\t// There will be a long time of needing both cases, until LTS advances to that point.\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t}\n}\n\n/**\n * Before submitting an op to the Outbox, its contents must be serialized using this function.\n * @remarks - The deserialization on process happens via the function {@link ensureContentsDeserialized}.\n */\nexport function serializeOp(op: LocalContainerRuntimeMessage): string {\n\treturn JSON.stringify(\n\t\top,\n\t\t// replacer:\n\t\t(key, value: unknown) => {\n\t\t\t// If 'value' is an IFluidHandle return its encoded form.\n\t\t\tif (isFluidHandle(value)) {\n\t\t\t\treturn encodeHandleForSerialization(toFluidHandleInternal(value));\n\t\t\t}\n\t\t\treturn value;\n\t\t},\n\t);\n}\n"]}
@@ -5,7 +5,7 @@
5
5
  import { IBatchMessage } from "@fluidframework/container-definitions/internal";
6
6
  import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
7
7
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
8
- import { BatchMessage, IBatch, IChunkedOp } from "./definitions.js";
8
+ import { IChunkedOp, type OutboundBatchMessage, type OutboundSingletonBatch } from "./definitions.js";
9
9
  export declare function isChunkedMessage(message: ISequencedDocumentMessage): boolean;
10
10
  /**
11
11
  * Responsible for creating and reconstructing chunked messages.
@@ -22,8 +22,8 @@ export declare class OpSplitter {
22
22
  clearPartialChunks(clientId: string): void;
23
23
  private addChunk;
24
24
  /**
25
- * Splits the first op of a compressed batch in chunks, sends the chunks separately and
26
- * returns a new batch composed of the last chunk and the rest of the ops in the original batch.
25
+ * Takes a singleton batch, and splits the interior message into chunks, sending the chunks separately and
26
+ * returning a new singleton batch containing the last chunk.
27
27
  *
28
28
  * A compressed batch is formed by one large op at the first position.
29
29
  *
@@ -34,21 +34,20 @@ export declare class OpSplitter {
34
34
  * This will ensure that the batch semantics of the original (non-compressed) batch are preserved, as the original chunked op
35
35
  * will be unrolled by the runtime when the first message in the batch is processed (as it is the last chunk).
36
36
  *
37
- * To handle legacy compressed batches with empty placeholders this method can attach the empty placeholder ops at the end
38
- * of the result batch, ensuring that the batch semantics are preserved.
39
- *
40
37
  * To illustrate the current functionality, if the input is `[largeOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.
41
38
  * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4]` will be returned.
42
39
  *
43
- * With the legacy code, if the input is `[largeOp, emptyOp, emptyOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.
44
- * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4, emptyOp, emptyOp]` will be returned.
45
- *
46
40
  * @remarks - A side effect here is that 1 or more chunks are queued immediately for sending in next JS turn.
47
41
  *
48
- * @param batch - the compressed batch which needs to be processed
49
- * @returns A batch with the last chunk of the original message
42
+ * @privateRemarks
43
+ * This maintains support for splitting a compressed batch with multiple messages (empty placeholders after the first),
44
+ * but this is only used for Unit Tests so the typing has been updated to preclude that.
45
+ * That code should be moved out of this function into a test helper.
46
+ *
47
+ * @param batch - the compressed batch which needs to be split into chunks before being sent over the wire
48
+ * @returns A batch with the last chunk in place of the original complete compressed content
50
49
  */
51
- splitFirstBatchMessage(batch: IBatch): IBatch;
50
+ splitSingletonBatchMessage(batch: OutboundSingletonBatch): OutboundSingletonBatch;
52
51
  processChunk(message: ISequencedDocumentMessage): ProcessChunkResult;
53
52
  }
54
53
  type ProcessChunkResult = {
@@ -69,6 +68,6 @@ type ProcessChunkResult = {
69
68
  * @param extraOp - should an extra empty op be added to the result
70
69
  * @returns an array of chunked ops
71
70
  */
72
- export declare const splitOp: (op: BatchMessage, chunkSizeInBytes: number, extraOp?: boolean) => IChunkedOp[];
71
+ export declare const splitOp: (op: OutboundBatchMessage, chunkSizeInBytes: number, extraOp?: boolean) => IChunkedOp[];
73
72
  export {};
74
73
  //# sourceMappingURL=opSplitter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"opSplitter.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opSplitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gDAAgD,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAEvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAWxF,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEpE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAE5E;AAWD;;GAEG;AACH,qBAAa,UAAU;IAOrB,OAAO,CAAC,QAAQ,CAAC,aAAa;aAGd,gBAAgB,EAAE,MAAM;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IATrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAG5C,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,EACX,aAAa,EAC3B,CAAC,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,uBAAuB,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GACtE,SAAS,EACI,gBAAgB,EAAE,MAAM,EACvB,mBAAmB,EAAE,MAAM,EAC5C,MAAM,EAAE,oBAAoB;IAM7B,IAAW,sBAAsB,IAAI,OAAO,CAI3C;IAED,IAAW,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAEjD;IAEM,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMjD,OAAO,CAAC,QAAQ;IA0BhB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACI,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAmE7C,YAAY,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB;CAuC3E;AAED,KAAK,kBAAkB,GACpB;IACA,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC;CAC5B,GACD;IACA,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;CAC3C,CAAC;AAkBL;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,OAAO,OACf,YAAY,oBACE,MAAM,YACf,OAAO,KACd,UAAU,EAkDZ,CAAC"}
1
+ {"version":3,"file":"opSplitter.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/opSplitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gDAAgD,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAEvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAUxF,OAAO,EACN,UAAU,EACV,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,MAAM,kBAAkB,CAAC;AAG1B,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAE5E;AAWD;;GAEG;AACH,qBAAa,UAAU;IAOrB,OAAO,CAAC,QAAQ,CAAC,aAAa;aAGd,gBAAgB,EAAE,MAAM;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IATrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAG5C,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,EACX,aAAa,EAC3B,CAAC,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,uBAAuB,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GACtE,SAAS,EACI,gBAAgB,EAAE,MAAM,EACvB,mBAAmB,EAAE,MAAM,EAC5C,MAAM,EAAE,oBAAoB;IAM7B,IAAW,sBAAsB,IAAI,OAAO,CAI3C;IAED,IAAW,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAEjD;IAEM,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMjD,OAAO,CAAC,QAAQ;IA0BhB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACI,0BAA0B,CAAC,KAAK,EAAE,sBAAsB,GAAG,sBAAsB;IAoEjF,YAAY,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB;CAuC3E;AAED,KAAK,kBAAkB,GACpB;IACA,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC;CAC5B,GACD;IACA,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;CAC3C,CAAC;AAkBL;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,OAAO,OACf,oBAAoB,oBACN,MAAM,YACf,OAAO,KACd,UAAU,EAkDZ,CAAC"}
@@ -8,7 +8,7 @@ exports.splitOp = exports.OpSplitter = exports.isChunkedMessage = void 0;
8
8
  const internal_1 = require("@fluidframework/core-utils/internal");
9
9
  const internal_2 = require("@fluidframework/telemetry-utils/internal");
10
10
  const messageTypes_js_1 = require("../messageTypes.js");
11
- const batchManager_js_1 = require("./batchManager.js");
11
+ const outbox_js_1 = require("./outbox.js");
12
12
  function isChunkedMessage(message) {
13
13
  return isChunkedContents(message.contents);
14
14
  }
@@ -58,8 +58,8 @@ class OpSplitter {
58
58
  map.push(chunkedContent.contents);
59
59
  }
60
60
  /**
61
- * Splits the first op of a compressed batch in chunks, sends the chunks separately and
62
- * returns a new batch composed of the last chunk and the rest of the ops in the original batch.
61
+ * Takes a singleton batch, and splits the interior message into chunks, sending the chunks separately and
62
+ * returning a new singleton batch containing the last chunk.
63
63
  *
64
64
  * A compressed batch is formed by one large op at the first position.
65
65
  *
@@ -70,30 +70,30 @@ class OpSplitter {
70
70
  * This will ensure that the batch semantics of the original (non-compressed) batch are preserved, as the original chunked op
71
71
  * will be unrolled by the runtime when the first message in the batch is processed (as it is the last chunk).
72
72
  *
73
- * To handle legacy compressed batches with empty placeholders this method can attach the empty placeholder ops at the end
74
- * of the result batch, ensuring that the batch semantics are preserved.
75
- *
76
73
  * To illustrate the current functionality, if the input is `[largeOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.
77
74
  * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4]` will be returned.
78
75
  *
79
- * With the legacy code, if the input is `[largeOp, emptyOp, emptyOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.
80
- * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4, emptyOp, emptyOp]` will be returned.
81
- *
82
76
  * @remarks - A side effect here is that 1 or more chunks are queued immediately for sending in next JS turn.
83
77
  *
84
- * @param batch - the compressed batch which needs to be processed
85
- * @returns A batch with the last chunk of the original message
78
+ * @privateRemarks
79
+ * This maintains support for splitting a compressed batch with multiple messages (empty placeholders after the first),
80
+ * but this is only used for Unit Tests so the typing has been updated to preclude that.
81
+ * That code should be moved out of this function into a test helper.
82
+ *
83
+ * @param batch - the compressed batch which needs to be split into chunks before being sent over the wire
84
+ * @returns A batch with the last chunk in place of the original complete compressed content
86
85
  */
87
- splitFirstBatchMessage(batch) {
86
+ splitSingletonBatchMessage(batch) {
88
87
  (0, internal_1.assert)(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);
89
88
  (0, internal_1.assert)(batch.contentSizeInBytes > 0 && batch.messages.length > 0, 0x514 /* Batch needs to be non-empty */);
90
89
  (0, internal_1.assert)(batch.referenceSequenceNumber !== undefined, 0x58a /* Batch must have a reference sequence number if non-empty */);
91
90
  (0, internal_1.assert)(this.chunkSizeInBytes !== 0, 0x515 /* Chunk size needs to be non-zero */);
92
91
  (0, internal_1.assert)(this.chunkSizeInBytes < this.maxBatchSizeInBytes, 0x516 /* Chunk size needs to be smaller than the max batch size */);
93
- const firstMessage = batch.messages[0]; // we expect this to be the large compressed op, which needs to be split
92
+ // first message is the large compressed op to split, and we expect restOfMessages to be empty
93
+ // (but we keep it here to support a legacy test case, wherein it contains empty placeholder messages)
94
+ const [firstMessage, ...restOfMessages] = batch.messages;
94
95
  (0, internal_1.assert)((firstMessage.contents?.length ?? 0) >= this.chunkSizeInBytes, 0x518 /* First message in the batch needs to be chunkable */);
95
- const restOfMessages = batch.messages.slice(1); // we expect these to be empty ops, created to reserve sequence numbers
96
- const socketSize = (0, batchManager_js_1.estimateSocketSize)(batch);
96
+ const socketSize = (0, outbox_js_1.estimateSocketSize)(batch);
97
97
  const chunks = (0, exports.splitOp)(firstMessage, this.chunkSizeInBytes,
98
98
  // If we estimate that the socket batch size will exceed the batch limit
99
99
  // we will inject an empty op to minimize the risk of the payload failing due to
@@ -1 +1 @@
1
- {"version":3,"file":"opSplitter.js","sourceRoot":"","sources":["../../src/opLifecycle/opSplitter.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IBatchMessage } from \"@fluidframework/container-definitions/internal\";\nimport { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tDataCorruptionError,\n\tcreateChildLogger,\n\textractSafePropertiesFromMessage,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { ContainerMessageType, ContainerRuntimeChunkedOpMessage } from \"../messageTypes.js\";\n\nimport { estimateSocketSize } from \"./batchManager.js\";\nimport { BatchMessage, IBatch, IChunkedOp } from \"./definitions.js\";\n\nexport function isChunkedMessage(message: ISequencedDocumentMessage): boolean {\n\treturn isChunkedContents(message.contents);\n}\n\ninterface IChunkedContents {\n\treadonly type: typeof ContainerMessageType.ChunkedOp;\n\treadonly contents: IChunkedOp;\n}\n\nfunction isChunkedContents(contents: unknown): contents is IChunkedContents {\n\treturn (contents as Partial<IChunkedContents>)?.type === ContainerMessageType.ChunkedOp;\n}\n\n/**\n * Responsible for creating and reconstructing chunked messages.\n */\nexport class OpSplitter {\n\t// Local copy of incomplete received chunks.\n\tprivate readonly chunkMap: Map<string, string[]>;\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(\n\t\tchunks: [string, string[]][],\n\t\tprivate readonly submitBatchFn:\n\t\t\t| ((batch: IBatchMessage[], referenceSequenceNumber?: number) => number)\n\t\t\t| undefined,\n\t\tpublic readonly chunkSizeInBytes: number,\n\t\tprivate readonly maxBatchSizeInBytes: number,\n\t\tlogger: ITelemetryBaseLogger,\n\t) {\n\t\tthis.chunkMap = new Map<string, string[]>(chunks);\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpSplitter\" });\n\t}\n\n\tpublic get isBatchChunkingEnabled(): boolean {\n\t\treturn (\n\t\t\tthis.chunkSizeInBytes < Number.POSITIVE_INFINITY && this.submitBatchFn !== undefined\n\t\t);\n\t}\n\n\tpublic get chunks(): ReadonlyMap<string, string[]> {\n\t\treturn this.chunkMap;\n\t}\n\n\tpublic clearPartialChunks(clientId: string): void {\n\t\tif (this.chunkMap.has(clientId)) {\n\t\t\tthis.chunkMap.delete(clientId);\n\t\t}\n\t}\n\n\tprivate addChunk(\n\t\tclientId: string,\n\t\tchunkedContent: IChunkedOp,\n\t\toriginalMessage: ISequencedDocumentMessage,\n\t): void {\n\t\tlet map = this.chunkMap.get(clientId);\n\t\tif (map === undefined) {\n\t\t\tmap = [];\n\t\t\tthis.chunkMap.set(clientId, map);\n\t\t}\n\n\t\tif (chunkedContent.chunkId !== map.length + 1) {\n\t\t\t// We are expecting the chunks to be processed sequentially, in the same order as they are sent.\n\t\t\t// Therefore, the chunkId of the incoming op needs to match the length of the array (1-based indexing)\n\t\t\t// holding the existing chunks for that particular clientId.\n\t\t\tthrow new DataCorruptionError(\"Chunk Id mismatch\", {\n\t\t\t\t...extractSafePropertiesFromMessage(originalMessage),\n\t\t\t\tchunkMapLength: map.length,\n\t\t\t\tchunkId: chunkedContent.chunkId,\n\t\t\t\ttotalChunks: chunkedContent.totalChunks,\n\t\t\t});\n\t\t}\n\n\t\tmap.push(chunkedContent.contents);\n\t}\n\n\t/**\n\t * Splits the first op of a compressed batch in chunks, sends the chunks separately and\n\t * returns a new batch composed of the last chunk and the rest of the ops in the original batch.\n\t *\n\t * A compressed batch is formed by one large op at the first position.\n\t *\n\t * If the op is too large, it can be chunked (split into smaller op) which can be sent individually over the wire\n\t * and accumulate at ingestion, until the last op in the chunk is processed, when the original op is unrolled.\n\t *\n\t * This method will send the first N - 1 chunks separately and use the last chunk as the first message in the result batch.\n\t * This will ensure that the batch semantics of the original (non-compressed) batch are preserved, as the original chunked op\n\t * will be unrolled by the runtime when the first message in the batch is processed (as it is the last chunk).\n\t *\n\t * To handle legacy compressed batches with empty placeholders this method can attach the empty placeholder ops at the end\n\t * of the result batch, ensuring that the batch semantics are preserved.\n\t *\n\t * To illustrate the current functionality, if the input is `[largeOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.\n\t * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4]` will be returned.\n\t *\n\t * With the legacy code, if the input is `[largeOp, emptyOp, emptyOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.\n\t * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4, emptyOp, emptyOp]` will be returned.\n\t *\n\t * @remarks - A side effect here is that 1 or more chunks are queued immediately for sending in next JS turn.\n\t *\n\t * @param batch - the compressed batch which needs to be processed\n\t * @returns A batch with the last chunk of the original message\n\t */\n\tpublic splitFirstBatchMessage(batch: IBatch): IBatch {\n\t\tassert(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);\n\t\tassert(\n\t\t\tbatch.contentSizeInBytes > 0 && batch.messages.length > 0,\n\t\t\t0x514 /* Batch needs to be non-empty */,\n\t\t);\n\t\tassert(\n\t\t\tbatch.referenceSequenceNumber !== undefined,\n\t\t\t0x58a /* Batch must have a reference sequence number if non-empty */,\n\t\t);\n\t\tassert(this.chunkSizeInBytes !== 0, 0x515 /* Chunk size needs to be non-zero */);\n\t\tassert(\n\t\t\tthis.chunkSizeInBytes < this.maxBatchSizeInBytes,\n\t\t\t0x516 /* Chunk size needs to be smaller than the max batch size */,\n\t\t);\n\n\t\tconst firstMessage = batch.messages[0]; // we expect this to be the large compressed op, which needs to be split\n\t\tassert(\n\t\t\t(firstMessage.contents?.length ?? 0) >= this.chunkSizeInBytes,\n\t\t\t0x518 /* First message in the batch needs to be chunkable */,\n\t\t);\n\n\t\tconst restOfMessages = batch.messages.slice(1); // we expect these to be empty ops, created to reserve sequence numbers\n\t\tconst socketSize = estimateSocketSize(batch);\n\t\tconst chunks = splitOp(\n\t\t\tfirstMessage,\n\t\t\tthis.chunkSizeInBytes,\n\t\t\t// If we estimate that the socket batch size will exceed the batch limit\n\t\t\t// we will inject an empty op to minimize the risk of the payload failing due to\n\t\t\t// the overhead from the trailing empty ops in the batch.\n\t\t\tsocketSize >= this.maxBatchSizeInBytes,\n\t\t);\n\n\t\tassert(this.submitBatchFn !== undefined, 0x519 /* We don't support old loaders */);\n\t\t// Send the first N-1 chunks immediately\n\t\tfor (const chunk of chunks.slice(0, -1)) {\n\t\t\tthis.submitBatchFn(\n\t\t\t\t[chunkToBatchMessage(chunk, batch.referenceSequenceNumber)],\n\t\t\t\tbatch.referenceSequenceNumber,\n\t\t\t);\n\t\t}\n\n\t\t// The last chunk will be part of the new batch and needs to\n\t\t// preserve the batch metadata of the original batch\n\t\tconst lastChunk = chunkToBatchMessage(\n\t\t\tchunks[chunks.length - 1],\n\t\t\tbatch.referenceSequenceNumber,\n\t\t\t{ batch: firstMessage.metadata?.batch },\n\t\t);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\t// Used to be \"Chunked compressed batch\"\n\t\t\teventName: \"CompressedChunkedBatch\",\n\t\t\tlength: batch.messages.length,\n\t\t\tsizeInBytes: batch.contentSizeInBytes,\n\t\t\tchunks: chunks.length,\n\t\t\tchunkSizeInBytes: this.chunkSizeInBytes,\n\t\t\tsocketSize,\n\t\t});\n\n\t\treturn {\n\t\t\tmessages: [lastChunk, ...restOfMessages],\n\t\t\tcontentSizeInBytes: lastChunk.contents?.length ?? 0,\n\t\t\treferenceSequenceNumber: batch.referenceSequenceNumber,\n\t\t};\n\t}\n\n\tpublic processChunk(message: ISequencedDocumentMessage): ProcessChunkResult {\n\t\tassert(isChunkedContents(message.contents), 0x948 /* message not of type ChunkedOp */);\n\t\tconst contents: IChunkedContents = message.contents;\n\n\t\t// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)\n\n\t\tconst clientId = message.clientId as string;\n\t\tconst chunkedContent = contents.contents;\n\t\tthis.addChunk(clientId, chunkedContent, message);\n\n\t\tif (chunkedContent.chunkId < chunkedContent.totalChunks) {\n\t\t\t// We are processing the op in chunks but haven't reached\n\t\t\t// the last chunk yet in order to reconstruct the original op\n\t\t\treturn {\n\t\t\t\tisFinalChunk: false,\n\t\t\t};\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tconst serializedContent = this.chunkMap.get(clientId)!.join(\"\");\n\t\tthis.clearPartialChunks(clientId);\n\n\t\t// The final/complete message will contain the data from all the chunks.\n\t\t// It will have the sequenceNumber of the last chunk\n\t\tconst completeMessage = { ...message };\n\t\tcompleteMessage.contents =\n\t\t\tserializedContent === \"\" ? undefined : JSON.parse(serializedContent);\n\t\t// back-compat with 1.x builds\n\t\t// This is only required / present for non-compressed, chunked ops\n\t\t// For compressed ops, we have op grouping enabled, and type of each op is preserved within compressed content.\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment\n\t\tcompleteMessage.type = (chunkedContent as any).originalType;\n\t\tcompleteMessage.metadata = chunkedContent.originalMetadata;\n\t\tcompleteMessage.compression = chunkedContent.originalCompression;\n\t\treturn {\n\t\t\tmessage: completeMessage,\n\t\t\tisFinalChunk: true,\n\t\t};\n\t}\n}\n\ntype ProcessChunkResult =\n\t| {\n\t\t\treadonly isFinalChunk: false;\n\t }\n\t| {\n\t\t\treadonly isFinalChunk: true;\n\t\t\treadonly message: ISequencedDocumentMessage;\n\t };\n\nconst chunkToBatchMessage = (\n\tchunk: IChunkedOp,\n\treferenceSequenceNumber: number,\n\tmetadata: Record<string, unknown> | undefined = undefined,\n): BatchMessage => {\n\tconst payload: ContainerRuntimeChunkedOpMessage = {\n\t\ttype: ContainerMessageType.ChunkedOp,\n\t\tcontents: chunk,\n\t};\n\treturn {\n\t\tcontents: JSON.stringify(payload),\n\t\tmetadata,\n\t\treferenceSequenceNumber,\n\t};\n};\n\n/**\n * Splits an op into smaller ops (chunks), based on the size of the op and the `chunkSizeInBytes` parameter.\n *\n * The last op of the result will be bundled with empty ops in the same batch. There is a risk of the batch payload\n * exceeding the 1MB limit due to the overhead from the empty ops. If the last op is large, the risk is even higher.\n * To minimize the odds, an extra empty op can be added to the result using the `extraOp` parameter.\n *\n * @param op - the op to be split\n * @param chunkSizeInBytes - how large should the chunks be\n * @param extraOp - should an extra empty op be added to the result\n * @returns an array of chunked ops\n */\nexport const splitOp = (\n\top: BatchMessage,\n\tchunkSizeInBytes: number,\n\textraOp: boolean = false,\n): IChunkedOp[] => {\n\tconst chunks: IChunkedOp[] = [];\n\tassert(\n\t\top.contents !== undefined && op.contents !== null,\n\t\t0x51a /* We should have something to chunk */,\n\t);\n\n\tconst contentLength = op.contents.length;\n\tconst chunkCount =\n\t\tMath.floor((contentLength - 1) / chunkSizeInBytes) + 1 + (extraOp ? 1 : 0);\n\tlet offset = 0;\n\tfor (let chunkId = 1; chunkId <= chunkCount; chunkId++) {\n\t\tconst chunk: IChunkedOp = {\n\t\t\tchunkId,\n\t\t\tcontents: op.contents.slice(offset, offset + chunkSizeInBytes),\n\t\t\ttotalChunks: chunkCount,\n\t\t};\n\n\t\tif (chunkId === chunkCount) {\n\t\t\t// We don't need to port these to all the chunks,\n\t\t\t// as we rebuild the original op when we process the\n\t\t\t// last chunk, therefore it is the only one that needs it.\n\t\t\tchunk.originalMetadata = op.metadata;\n\t\t\tchunk.originalCompression = op.compression;\n\n\t\t\t// back-compat with 1.x builds\n\t\t\t// 2.x builds only do chunking for compressed ops.\n\t\t\t// originalType is no longer used in such cases, as each op preserves its type within compressed payload.\n\t\t\t// But, if 1.x builds see this op, and there is no type on the message, then it will ignore this message silently.\n\t\t\t// This is really bad, as we will crash on later ops and it's very hard to debug these cases.\n\t\t\t// If we put some known type here, then we will crash on it (as 1.x does not understand compression, and thus will not\n\t\t\t// find info on the op like address of the channel to deliver the op)\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access\n\t\t\t(chunk as any).originalType = \"component\";\n\t\t}\n\n\t\tchunks.push(chunk);\n\t\toffset += chunkSizeInBytes;\n\t\tassert(\n\t\t\tchunkId >= chunkCount - 1 || offset <= contentLength,\n\t\t\t0x58b /* Content offset within bounds */,\n\t\t);\n\t}\n\n\tassert(\n\t\toffset >= contentLength,\n\t\t0x58c /* Content offset equal or larger than content length */,\n\t);\n\tassert(chunks.length === chunkCount, 0x5a5 /* Expected number of chunks */);\n\treturn chunks;\n};\n"]}
1
+ {"version":3,"file":"opSplitter.js","sourceRoot":"","sources":["../../src/opLifecycle/opSplitter.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAIH,kEAA6D;AAE7D,uEAKkD;AAElD,wDAA4F;AAO5F,2CAAiD;AAEjD,SAAgB,gBAAgB,CAAC,OAAkC;IAClE,OAAO,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAFD,4CAEC;AAOD,SAAS,iBAAiB,CAAC,QAAiB;IAC3C,OAAQ,QAAsC,EAAE,IAAI,KAAK,sCAAoB,CAAC,SAAS,CAAC;AACzF,CAAC;AAED;;GAEG;AACH,MAAa,UAAU;IAKtB,YACC,MAA4B,EACX,aAEL,EACI,gBAAwB,EACvB,mBAA2B,EAC5C,MAA4B;QALX,kBAAa,GAAb,aAAa,CAElB;QACI,qBAAgB,GAAhB,gBAAgB,CAAQ;QACvB,wBAAmB,GAAnB,mBAAmB,CAAQ;QAG5C,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAmB,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,GAAG,IAAA,4BAAiB,EAAC,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,IAAW,sBAAsB;QAChC,OAAO,CACN,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,iBAAiB,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,CACpF,CAAC;IACH,CAAC;IAED,IAAW,MAAM;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAEM,kBAAkB,CAAC,QAAgB;QACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;IACF,CAAC;IAEO,QAAQ,CACf,QAAgB,EAChB,cAA0B,EAC1B,eAA0C;QAE1C,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACvB,GAAG,GAAG,EAAE,CAAC;YACT,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,cAAc,CAAC,OAAO,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/C,gGAAgG;YAChG,sGAAsG;YACtG,4DAA4D;YAC5D,MAAM,IAAI,8BAAmB,CAAC,mBAAmB,EAAE;gBAClD,GAAG,IAAA,2CAAgC,EAAC,eAAe,CAAC;gBACpD,cAAc,EAAE,GAAG,CAAC,MAAM;gBAC1B,OAAO,EAAE,cAAc,CAAC,OAAO;gBAC/B,WAAW,EAAE,cAAc,CAAC,WAAW;aACvC,CAAC,CAAC;QACJ,CAAC;QAED,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACI,0BAA0B,CAAC,KAA6B;QAC9D,IAAA,iBAAM,EAAC,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC9E,IAAA,iBAAM,EACL,KAAK,CAAC,kBAAkB,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EACzD,KAAK,CAAC,iCAAiC,CACvC,CAAC;QACF,IAAA,iBAAM,EACL,KAAK,CAAC,uBAAuB,KAAK,SAAS,EAC3C,KAAK,CAAC,8DAA8D,CACpE,CAAC;QACF,IAAA,iBAAM,EAAC,IAAI,CAAC,gBAAgB,KAAK,CAAC,EAAE,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACjF,IAAA,iBAAM,EACL,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,mBAAmB,EAChD,KAAK,CAAC,4DAA4D,CAClE,CAAC;QAEF,8FAA8F;QAC9F,sGAAsG;QACtG,MAAM,CAAC,YAAY,EAAE,GAAG,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC;QACzD,IAAA,iBAAM,EACL,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,gBAAgB,EAC7D,KAAK,CAAC,sDAAsD,CAC5D,CAAC;QAEF,MAAM,UAAU,GAAG,IAAA,8BAAkB,EAAC,KAAK,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,IAAA,eAAO,EACrB,YAAY,EACZ,IAAI,CAAC,gBAAgB;QACrB,wEAAwE;QACxE,gFAAgF;QAChF,yDAAyD;QACzD,UAAU,IAAI,IAAI,CAAC,mBAAmB,CACtC,CAAC;QAEF,IAAA,iBAAM,EAAC,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACnF,wCAAwC;QACxC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,aAAa,CACjB,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAC,EAC3D,KAAK,CAAC,uBAAuB,CAC7B,CAAC;QACH,CAAC;QAED,4DAA4D;QAC5D,oDAAoD;QACpD,MAAM,SAAS,GAAG,mBAAmB,CACpC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,EACzB,KAAK,CAAC,uBAAuB,EAC7B,EAAE,KAAK,EAAE,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,CACvC,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;YAChC,wCAAwC;YACxC,SAAS,EAAE,wBAAwB;YACnC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;YAC7B,WAAW,EAAE,KAAK,CAAC,kBAAkB;YACrC,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,UAAU;SACV,CAAC,CAAC;QAEH,OAAO;YACN,QAAQ,EAAE,CAAC,SAAS,EAAE,GAAG,cAAc,CAAC;YACxC,kBAAkB,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;YACnD,uBAAuB,EAAE,KAAK,CAAC,uBAAuB;SACtD,CAAC;IACH,CAAC;IAEM,YAAY,CAAC,OAAkC;QACrD,IAAA,iBAAM,EAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACvF,MAAM,QAAQ,GAAqB,OAAO,CAAC,QAAQ,CAAC;QAEpD,+FAA+F;QAE/F,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAkB,CAAC;QAC5C,MAAM,cAAc,GAAG,QAAQ,CAAC,QAAQ,CAAC;QACzC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;QAEjD,IAAI,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC;YACzD,yDAAyD;YACzD,6DAA6D;YAC7D,OAAO;gBACN,YAAY,EAAE,KAAK;aACnB,CAAC;QACH,CAAC;QAED,oEAAoE;QACpE,MAAM,iBAAiB,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAElC,wEAAwE;QACxE,oDAAoD;QACpD,MAAM,eAAe,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;QACvC,eAAe,CAAC,QAAQ;YACvB,iBAAiB,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACtE,8BAA8B;QAC9B,kEAAkE;QAClE,+GAA+G;QAC/G,mJAAmJ;QACnJ,eAAe,CAAC,IAAI,GAAI,cAAsB,CAAC,YAAY,CAAC;QAC5D,eAAe,CAAC,QAAQ,GAAG,cAAc,CAAC,gBAAgB,CAAC;QAC3D,eAAe,CAAC,WAAW,GAAG,cAAc,CAAC,mBAAmB,CAAC;QACjE,OAAO;YACN,OAAO,EAAE,eAAe;YACxB,YAAY,EAAE,IAAI;SAClB,CAAC;IACH,CAAC;CACD;AAjMD,gCAiMC;AAWD,MAAM,mBAAmB,GAAG,CAC3B,KAAiB,EACjB,uBAA+B,EAC/B,WAAgD,SAAS,EAClC,EAAE;IACzB,MAAM,OAAO,GAAqC;QACjD,IAAI,EAAE,sCAAoB,CAAC,SAAS;QACpC,QAAQ,EAAE,KAAK;KACf,CAAC;IACF,OAAO;QACN,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;QACjC,QAAQ;QACR,uBAAuB;KACvB,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;;;;;GAWG;AACI,MAAM,OAAO,GAAG,CACtB,EAAwB,EACxB,gBAAwB,EACxB,UAAmB,KAAK,EACT,EAAE;IACjB,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAA,iBAAM,EACL,EAAE,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,CAAC,QAAQ,KAAK,IAAI,EACjD,KAAK,CAAC,uCAAuC,CAC7C,CAAC;IAEF,MAAM,aAAa,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;IACzC,MAAM,UAAU,GACf,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACxD,MAAM,KAAK,GAAe;YACzB,OAAO;YACP,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAAC;YAC9D,WAAW,EAAE,UAAU;SACvB,CAAC;QAEF,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;YAC5B,iDAAiD;YACjD,oDAAoD;YACpD,0DAA0D;YAC1D,KAAK,CAAC,gBAAgB,GAAG,EAAE,CAAC,QAAQ,CAAC;YACrC,KAAK,CAAC,mBAAmB,GAAG,EAAE,CAAC,WAAW,CAAC;YAE3C,8BAA8B;YAC9B,kDAAkD;YAClD,yGAAyG;YACzG,kHAAkH;YAClH,6FAA6F;YAC7F,sHAAsH;YACtH,qEAAqE;YACrE,0GAA0G;YACzG,KAAa,CAAC,YAAY,GAAG,WAAW,CAAC;QAC3C,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,MAAM,IAAI,gBAAgB,CAAC;QAC3B,IAAA,iBAAM,EACL,OAAO,IAAI,UAAU,GAAG,CAAC,IAAI,MAAM,IAAI,aAAa,EACpD,KAAK,CAAC,kCAAkC,CACxC,CAAC;IACH,CAAC;IAED,IAAA,iBAAM,EACL,MAAM,IAAI,aAAa,EACvB,KAAK,CAAC,wDAAwD,CAC9D,CAAC;IACF,IAAA,iBAAM,EAAC,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAC5E,OAAO,MAAM,CAAC;AACf,CAAC,CAAC;AAtDW,QAAA,OAAO,WAsDlB","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IBatchMessage } from \"@fluidframework/container-definitions/internal\";\nimport { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tDataCorruptionError,\n\tcreateChildLogger,\n\textractSafePropertiesFromMessage,\n\ttype ITelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { ContainerMessageType, ContainerRuntimeChunkedOpMessage } from \"../messageTypes.js\";\n\nimport {\n\tIChunkedOp,\n\ttype OutboundBatchMessage,\n\ttype OutboundSingletonBatch,\n} from \"./definitions.js\";\nimport { estimateSocketSize } from \"./outbox.js\";\n\nexport function isChunkedMessage(message: ISequencedDocumentMessage): boolean {\n\treturn isChunkedContents(message.contents);\n}\n\ninterface IChunkedContents {\n\treadonly type: typeof ContainerMessageType.ChunkedOp;\n\treadonly contents: IChunkedOp;\n}\n\nfunction isChunkedContents(contents: unknown): contents is IChunkedContents {\n\treturn (contents as Partial<IChunkedContents>)?.type === ContainerMessageType.ChunkedOp;\n}\n\n/**\n * Responsible for creating and reconstructing chunked messages.\n */\nexport class OpSplitter {\n\t// Local copy of incomplete received chunks.\n\tprivate readonly chunkMap: Map<string, string[]>;\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(\n\t\tchunks: [string, string[]][],\n\t\tprivate readonly submitBatchFn:\n\t\t\t| ((batch: IBatchMessage[], referenceSequenceNumber?: number) => number)\n\t\t\t| undefined,\n\t\tpublic readonly chunkSizeInBytes: number,\n\t\tprivate readonly maxBatchSizeInBytes: number,\n\t\tlogger: ITelemetryBaseLogger,\n\t) {\n\t\tthis.chunkMap = new Map<string, string[]>(chunks);\n\t\tthis.logger = createChildLogger({ logger, namespace: \"OpSplitter\" });\n\t}\n\n\tpublic get isBatchChunkingEnabled(): boolean {\n\t\treturn (\n\t\t\tthis.chunkSizeInBytes < Number.POSITIVE_INFINITY && this.submitBatchFn !== undefined\n\t\t);\n\t}\n\n\tpublic get chunks(): ReadonlyMap<string, string[]> {\n\t\treturn this.chunkMap;\n\t}\n\n\tpublic clearPartialChunks(clientId: string): void {\n\t\tif (this.chunkMap.has(clientId)) {\n\t\t\tthis.chunkMap.delete(clientId);\n\t\t}\n\t}\n\n\tprivate addChunk(\n\t\tclientId: string,\n\t\tchunkedContent: IChunkedOp,\n\t\toriginalMessage: ISequencedDocumentMessage,\n\t): void {\n\t\tlet map = this.chunkMap.get(clientId);\n\t\tif (map === undefined) {\n\t\t\tmap = [];\n\t\t\tthis.chunkMap.set(clientId, map);\n\t\t}\n\n\t\tif (chunkedContent.chunkId !== map.length + 1) {\n\t\t\t// We are expecting the chunks to be processed sequentially, in the same order as they are sent.\n\t\t\t// Therefore, the chunkId of the incoming op needs to match the length of the array (1-based indexing)\n\t\t\t// holding the existing chunks for that particular clientId.\n\t\t\tthrow new DataCorruptionError(\"Chunk Id mismatch\", {\n\t\t\t\t...extractSafePropertiesFromMessage(originalMessage),\n\t\t\t\tchunkMapLength: map.length,\n\t\t\t\tchunkId: chunkedContent.chunkId,\n\t\t\t\ttotalChunks: chunkedContent.totalChunks,\n\t\t\t});\n\t\t}\n\n\t\tmap.push(chunkedContent.contents);\n\t}\n\n\t/**\n\t * Takes a singleton batch, and splits the interior message into chunks, sending the chunks separately and\n\t * returning a new singleton batch containing the last chunk.\n\t *\n\t * A compressed batch is formed by one large op at the first position.\n\t *\n\t * If the op is too large, it can be chunked (split into smaller op) which can be sent individually over the wire\n\t * and accumulate at ingestion, until the last op in the chunk is processed, when the original op is unrolled.\n\t *\n\t * This method will send the first N - 1 chunks separately and use the last chunk as the first message in the result batch.\n\t * This will ensure that the batch semantics of the original (non-compressed) batch are preserved, as the original chunked op\n\t * will be unrolled by the runtime when the first message in the batch is processed (as it is the last chunk).\n\t *\n\t * To illustrate the current functionality, if the input is `[largeOp]`, `largeOp` will be split into `[chunk1, chunk2, chunk3, chunk4]`.\n\t * `chunk1`, `chunk2` and `chunk3` will be sent individually and `[chunk4]` will be returned.\n\t *\n\t * @remarks - A side effect here is that 1 or more chunks are queued immediately for sending in next JS turn.\n\t *\n\t * @privateRemarks\n\t * This maintains support for splitting a compressed batch with multiple messages (empty placeholders after the first),\n\t * but this is only used for Unit Tests so the typing has been updated to preclude that.\n\t * That code should be moved out of this function into a test helper.\n\t *\n\t * @param batch - the compressed batch which needs to be split into chunks before being sent over the wire\n\t * @returns A batch with the last chunk in place of the original complete compressed content\n\t */\n\tpublic splitSingletonBatchMessage(batch: OutboundSingletonBatch): OutboundSingletonBatch {\n\t\tassert(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);\n\t\tassert(\n\t\t\tbatch.contentSizeInBytes > 0 && batch.messages.length > 0,\n\t\t\t0x514 /* Batch needs to be non-empty */,\n\t\t);\n\t\tassert(\n\t\t\tbatch.referenceSequenceNumber !== undefined,\n\t\t\t0x58a /* Batch must have a reference sequence number if non-empty */,\n\t\t);\n\t\tassert(this.chunkSizeInBytes !== 0, 0x515 /* Chunk size needs to be non-zero */);\n\t\tassert(\n\t\t\tthis.chunkSizeInBytes < this.maxBatchSizeInBytes,\n\t\t\t0x516 /* Chunk size needs to be smaller than the max batch size */,\n\t\t);\n\n\t\t// first message is the large compressed op to split, and we expect restOfMessages to be empty\n\t\t// (but we keep it here to support a legacy test case, wherein it contains empty placeholder messages)\n\t\tconst [firstMessage, ...restOfMessages] = batch.messages;\n\t\tassert(\n\t\t\t(firstMessage.contents?.length ?? 0) >= this.chunkSizeInBytes,\n\t\t\t0x518 /* First message in the batch needs to be chunkable */,\n\t\t);\n\n\t\tconst socketSize = estimateSocketSize(batch);\n\t\tconst chunks = splitOp(\n\t\t\tfirstMessage,\n\t\t\tthis.chunkSizeInBytes,\n\t\t\t// If we estimate that the socket batch size will exceed the batch limit\n\t\t\t// we will inject an empty op to minimize the risk of the payload failing due to\n\t\t\t// the overhead from the trailing empty ops in the batch.\n\t\t\tsocketSize >= this.maxBatchSizeInBytes,\n\t\t);\n\n\t\tassert(this.submitBatchFn !== undefined, 0x519 /* We don't support old loaders */);\n\t\t// Send the first N-1 chunks immediately\n\t\tfor (const chunk of chunks.slice(0, -1)) {\n\t\t\tthis.submitBatchFn(\n\t\t\t\t[chunkToBatchMessage(chunk, batch.referenceSequenceNumber)],\n\t\t\t\tbatch.referenceSequenceNumber,\n\t\t\t);\n\t\t}\n\n\t\t// The last chunk will be part of the new batch and needs to\n\t\t// preserve the batch metadata of the original batch\n\t\tconst lastChunk = chunkToBatchMessage(\n\t\t\tchunks[chunks.length - 1],\n\t\t\tbatch.referenceSequenceNumber,\n\t\t\t{ batch: firstMessage.metadata?.batch },\n\t\t);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\t// Used to be \"Chunked compressed batch\"\n\t\t\teventName: \"CompressedChunkedBatch\",\n\t\t\tlength: batch.messages.length,\n\t\t\tsizeInBytes: batch.contentSizeInBytes,\n\t\t\tchunks: chunks.length,\n\t\t\tchunkSizeInBytes: this.chunkSizeInBytes,\n\t\t\tsocketSize,\n\t\t});\n\n\t\treturn {\n\t\t\tmessages: [lastChunk, ...restOfMessages],\n\t\t\tcontentSizeInBytes: lastChunk.contents?.length ?? 0,\n\t\t\treferenceSequenceNumber: batch.referenceSequenceNumber,\n\t\t};\n\t}\n\n\tpublic processChunk(message: ISequencedDocumentMessage): ProcessChunkResult {\n\t\tassert(isChunkedContents(message.contents), 0x948 /* message not of type ChunkedOp */);\n\t\tconst contents: IChunkedContents = message.contents;\n\n\t\t// TODO: Verify whether this should be able to handle server-generated ops (with null clientId)\n\n\t\tconst clientId = message.clientId as string;\n\t\tconst chunkedContent = contents.contents;\n\t\tthis.addChunk(clientId, chunkedContent, message);\n\n\t\tif (chunkedContent.chunkId < chunkedContent.totalChunks) {\n\t\t\t// We are processing the op in chunks but haven't reached\n\t\t\t// the last chunk yet in order to reconstruct the original op\n\t\t\treturn {\n\t\t\t\tisFinalChunk: false,\n\t\t\t};\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tconst serializedContent = this.chunkMap.get(clientId)!.join(\"\");\n\t\tthis.clearPartialChunks(clientId);\n\n\t\t// The final/complete message will contain the data from all the chunks.\n\t\t// It will have the sequenceNumber of the last chunk\n\t\tconst completeMessage = { ...message };\n\t\tcompleteMessage.contents =\n\t\t\tserializedContent === \"\" ? undefined : JSON.parse(serializedContent);\n\t\t// back-compat with 1.x builds\n\t\t// This is only required / present for non-compressed, chunked ops\n\t\t// For compressed ops, we have op grouping enabled, and type of each op is preserved within compressed content.\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment\n\t\tcompleteMessage.type = (chunkedContent as any).originalType;\n\t\tcompleteMessage.metadata = chunkedContent.originalMetadata;\n\t\tcompleteMessage.compression = chunkedContent.originalCompression;\n\t\treturn {\n\t\t\tmessage: completeMessage,\n\t\t\tisFinalChunk: true,\n\t\t};\n\t}\n}\n\ntype ProcessChunkResult =\n\t| {\n\t\t\treadonly isFinalChunk: false;\n\t }\n\t| {\n\t\t\treadonly isFinalChunk: true;\n\t\t\treadonly message: ISequencedDocumentMessage;\n\t };\n\nconst chunkToBatchMessage = (\n\tchunk: IChunkedOp,\n\treferenceSequenceNumber: number,\n\tmetadata: Record<string, unknown> | undefined = undefined,\n): OutboundBatchMessage => {\n\tconst payload: ContainerRuntimeChunkedOpMessage = {\n\t\ttype: ContainerMessageType.ChunkedOp,\n\t\tcontents: chunk,\n\t};\n\treturn {\n\t\tcontents: JSON.stringify(payload),\n\t\tmetadata,\n\t\treferenceSequenceNumber,\n\t};\n};\n\n/**\n * Splits an op into smaller ops (chunks), based on the size of the op and the `chunkSizeInBytes` parameter.\n *\n * The last op of the result will be bundled with empty ops in the same batch. There is a risk of the batch payload\n * exceeding the 1MB limit due to the overhead from the empty ops. If the last op is large, the risk is even higher.\n * To minimize the odds, an extra empty op can be added to the result using the `extraOp` parameter.\n *\n * @param op - the op to be split\n * @param chunkSizeInBytes - how large should the chunks be\n * @param extraOp - should an extra empty op be added to the result\n * @returns an array of chunked ops\n */\nexport const splitOp = (\n\top: OutboundBatchMessage,\n\tchunkSizeInBytes: number,\n\textraOp: boolean = false,\n): IChunkedOp[] => {\n\tconst chunks: IChunkedOp[] = [];\n\tassert(\n\t\top.contents !== undefined && op.contents !== null,\n\t\t0x51a /* We should have something to chunk */,\n\t);\n\n\tconst contentLength = op.contents.length;\n\tconst chunkCount =\n\t\tMath.floor((contentLength - 1) / chunkSizeInBytes) + 1 + (extraOp ? 1 : 0);\n\tlet offset = 0;\n\tfor (let chunkId = 1; chunkId <= chunkCount; chunkId++) {\n\t\tconst chunk: IChunkedOp = {\n\t\t\tchunkId,\n\t\t\tcontents: op.contents.slice(offset, offset + chunkSizeInBytes),\n\t\t\ttotalChunks: chunkCount,\n\t\t};\n\n\t\tif (chunkId === chunkCount) {\n\t\t\t// We don't need to port these to all the chunks,\n\t\t\t// as we rebuild the original op when we process the\n\t\t\t// last chunk, therefore it is the only one that needs it.\n\t\t\tchunk.originalMetadata = op.metadata;\n\t\t\tchunk.originalCompression = op.compression;\n\n\t\t\t// back-compat with 1.x builds\n\t\t\t// 2.x builds only do chunking for compressed ops.\n\t\t\t// originalType is no longer used in such cases, as each op preserves its type within compressed payload.\n\t\t\t// But, if 1.x builds see this op, and there is no type on the message, then it will ignore this message silently.\n\t\t\t// This is really bad, as we will crash on later ops and it's very hard to debug these cases.\n\t\t\t// If we put some known type here, then we will crash on it (as 1.x does not understand compression, and thus will not\n\t\t\t// find info on the op like address of the channel to deliver the op)\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access\n\t\t\t(chunk as any).originalType = \"component\";\n\t\t}\n\n\t\tchunks.push(chunk);\n\t\toffset += chunkSizeInBytes;\n\t\tassert(\n\t\t\tchunkId >= chunkCount - 1 || offset <= contentLength,\n\t\t\t0x58b /* Content offset within bounds */,\n\t\t);\n\t}\n\n\tassert(\n\t\toffset >= contentLength,\n\t\t0x58c /* Content offset equal or larger than content length */,\n\t);\n\tassert(chunks.length === chunkCount, 0x5a5 /* Expected number of chunks */);\n\treturn chunks;\n};\n"]}