@fluidframework/container-runtime 2.0.0-dev.4.1.0.148229 → 2.0.0-dev.4.3.0.157531

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 (314) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +69 -0
  3. package/dist/blobManager.d.ts +6 -14
  4. package/dist/blobManager.d.ts.map +1 -1
  5. package/dist/blobManager.js +50 -37
  6. package/dist/blobManager.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +47 -4
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +203 -49
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/dataStoreContext.d.ts +2 -1
  12. package/dist/dataStoreContext.d.ts.map +1 -1
  13. package/dist/dataStoreContext.js +3 -0
  14. package/dist/dataStoreContext.js.map +1 -1
  15. package/dist/dataStores.d.ts +5 -5
  16. package/dist/dataStores.d.ts.map +1 -1
  17. package/dist/dataStores.js +3 -6
  18. package/dist/dataStores.js.map +1 -1
  19. package/dist/gc/garbageCollection.d.ts.map +1 -1
  20. package/dist/gc/garbageCollection.js +5 -5
  21. package/dist/gc/garbageCollection.js.map +1 -1
  22. package/dist/gc/gcConfigs.d.ts.map +1 -1
  23. package/dist/gc/gcConfigs.js +1 -3
  24. package/dist/gc/gcConfigs.js.map +1 -1
  25. package/dist/gc/gcDefinitions.js +1 -1
  26. package/dist/gc/gcDefinitions.js.map +1 -1
  27. package/dist/gc/gcHelpers.d.ts.map +1 -1
  28. package/dist/gc/gcHelpers.js +6 -6
  29. package/dist/gc/gcHelpers.js.map +1 -1
  30. package/dist/id-compressor/appendOnlySortedMap.d.ts +146 -0
  31. package/dist/id-compressor/appendOnlySortedMap.d.ts.map +1 -0
  32. package/dist/id-compressor/appendOnlySortedMap.js +360 -0
  33. package/dist/id-compressor/appendOnlySortedMap.js.map +1 -0
  34. package/dist/id-compressor/idCompressor.d.ts +279 -0
  35. package/dist/id-compressor/idCompressor.d.ts.map +1 -0
  36. package/dist/id-compressor/idCompressor.js +1258 -0
  37. package/dist/id-compressor/idCompressor.js.map +1 -0
  38. package/dist/id-compressor/idRange.d.ts +11 -0
  39. package/dist/id-compressor/idRange.d.ts.map +1 -0
  40. package/dist/id-compressor/idRange.js +29 -0
  41. package/dist/id-compressor/idRange.js.map +1 -0
  42. package/dist/id-compressor/index.d.ts +14 -0
  43. package/dist/id-compressor/index.d.ts.map +1 -0
  44. package/dist/id-compressor/index.js +38 -0
  45. package/dist/id-compressor/index.js.map +1 -0
  46. package/dist/id-compressor/numericUuid.d.ts +59 -0
  47. package/dist/id-compressor/numericUuid.d.ts.map +1 -0
  48. package/dist/id-compressor/numericUuid.js +325 -0
  49. package/dist/id-compressor/numericUuid.js.map +1 -0
  50. package/dist/id-compressor/sessionIdNormalizer.d.ts +138 -0
  51. package/dist/id-compressor/sessionIdNormalizer.d.ts.map +1 -0
  52. package/dist/id-compressor/sessionIdNormalizer.js +488 -0
  53. package/dist/id-compressor/sessionIdNormalizer.js.map +1 -0
  54. package/dist/id-compressor/utils.d.ts +57 -0
  55. package/dist/id-compressor/utils.d.ts.map +1 -0
  56. package/dist/id-compressor/utils.js +90 -0
  57. package/dist/id-compressor/utils.js.map +1 -0
  58. package/dist/id-compressor/uuidUtilities.d.ts +30 -0
  59. package/dist/id-compressor/uuidUtilities.d.ts.map +1 -0
  60. package/dist/id-compressor/uuidUtilities.js +106 -0
  61. package/dist/id-compressor/uuidUtilities.js.map +1 -0
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +5 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/opLifecycle/batchManager.d.ts +9 -2
  67. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  68. package/dist/opLifecycle/batchManager.js +21 -2
  69. package/dist/opLifecycle/batchManager.js.map +1 -1
  70. package/dist/opLifecycle/index.d.ts +2 -1
  71. package/dist/opLifecycle/index.d.ts.map +1 -1
  72. package/dist/opLifecycle/index.js +3 -1
  73. package/dist/opLifecycle/index.js.map +1 -1
  74. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  75. package/dist/opLifecycle/opDecompressor.js +2 -1
  76. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  77. package/dist/opLifecycle/opGroupingManager.d.ts +14 -0
  78. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -0
  79. package/dist/opLifecycle/opGroupingManager.js +61 -0
  80. package/dist/opLifecycle/opGroupingManager.js.map +1 -0
  81. package/dist/opLifecycle/opSplitter.d.ts +1 -1
  82. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  83. package/dist/opLifecycle/opSplitter.js +5 -6
  84. package/dist/opLifecycle/opSplitter.js.map +1 -1
  85. package/dist/opLifecycle/outbox.d.ts +4 -2
  86. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  87. package/dist/opLifecycle/outbox.js +37 -25
  88. package/dist/opLifecycle/outbox.js.map +1 -1
  89. package/dist/opLifecycle/remoteMessageProcessor.d.ts +4 -2
  90. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  91. package/dist/opLifecycle/remoteMessageProcessor.js +30 -20
  92. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  93. package/dist/packageVersion.d.ts +1 -1
  94. package/dist/packageVersion.js +1 -1
  95. package/dist/packageVersion.js.map +1 -1
  96. package/dist/pendingStateManager.d.ts +1 -1
  97. package/dist/pendingStateManager.d.ts.map +1 -1
  98. package/dist/pendingStateManager.js +11 -3
  99. package/dist/pendingStateManager.js.map +1 -1
  100. package/dist/summary/index.d.ts +2 -2
  101. package/dist/summary/index.d.ts.map +1 -1
  102. package/dist/summary/index.js +4 -1
  103. package/dist/summary/index.js.map +1 -1
  104. package/dist/summary/orderedClientElection.d.ts +1 -0
  105. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  106. package/dist/summary/orderedClientElection.js +19 -0
  107. package/dist/summary/orderedClientElection.js.map +1 -1
  108. package/dist/summary/runningSummarizer.d.ts +4 -3
  109. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  110. package/dist/summary/runningSummarizer.js +65 -66
  111. package/dist/summary/runningSummarizer.js.map +1 -1
  112. package/dist/summary/summarizer.d.ts.map +1 -1
  113. package/dist/summary/summarizer.js +1 -5
  114. package/dist/summary/summarizer.js.map +1 -1
  115. package/dist/summary/summarizerHeuristics.d.ts +1 -0
  116. package/dist/summary/summarizerHeuristics.d.ts.map +1 -1
  117. package/dist/summary/summarizerHeuristics.js +3 -0
  118. package/dist/summary/summarizerHeuristics.js.map +1 -1
  119. package/dist/summary/summarizerNode/summarizerNode.js +1 -1
  120. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  121. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +128 -2
  122. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  123. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +4 -3
  124. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  125. package/dist/summary/summarizerTypes.d.ts +14 -2
  126. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  127. package/dist/summary/summarizerTypes.js.map +1 -1
  128. package/dist/summary/summaryFormat.d.ts +3 -0
  129. package/dist/summary/summaryFormat.d.ts.map +1 -1
  130. package/dist/summary/summaryFormat.js +3 -1
  131. package/dist/summary/summaryFormat.js.map +1 -1
  132. package/dist/summary/summaryGenerator.d.ts +28 -2
  133. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  134. package/dist/summary/summaryGenerator.js +19 -16
  135. package/dist/summary/summaryGenerator.js.map +1 -1
  136. package/dist/summary/summaryManager.d.ts.map +1 -1
  137. package/dist/summary/summaryManager.js +2 -0
  138. package/dist/summary/summaryManager.js.map +1 -1
  139. package/lib/blobManager.d.ts +6 -14
  140. package/lib/blobManager.d.ts.map +1 -1
  141. package/lib/blobManager.js +50 -37
  142. package/lib/blobManager.js.map +1 -1
  143. package/lib/containerRuntime.d.ts +47 -4
  144. package/lib/containerRuntime.d.ts.map +1 -1
  145. package/lib/containerRuntime.js +187 -52
  146. package/lib/containerRuntime.js.map +1 -1
  147. package/lib/dataStoreContext.d.ts +2 -1
  148. package/lib/dataStoreContext.d.ts.map +1 -1
  149. package/lib/dataStoreContext.js +3 -0
  150. package/lib/dataStoreContext.js.map +1 -1
  151. package/lib/dataStores.d.ts +5 -5
  152. package/lib/dataStores.d.ts.map +1 -1
  153. package/lib/dataStores.js +3 -6
  154. package/lib/dataStores.js.map +1 -1
  155. package/lib/gc/garbageCollection.d.ts.map +1 -1
  156. package/lib/gc/garbageCollection.js +5 -5
  157. package/lib/gc/garbageCollection.js.map +1 -1
  158. package/lib/gc/gcConfigs.d.ts.map +1 -1
  159. package/lib/gc/gcConfigs.js +1 -3
  160. package/lib/gc/gcConfigs.js.map +1 -1
  161. package/lib/gc/gcDefinitions.js +1 -1
  162. package/lib/gc/gcDefinitions.js.map +1 -1
  163. package/lib/gc/gcHelpers.d.ts.map +1 -1
  164. package/lib/gc/gcHelpers.js +6 -6
  165. package/lib/gc/gcHelpers.js.map +1 -1
  166. package/lib/id-compressor/appendOnlySortedMap.d.ts +146 -0
  167. package/lib/id-compressor/appendOnlySortedMap.d.ts.map +1 -0
  168. package/lib/id-compressor/appendOnlySortedMap.js +355 -0
  169. package/lib/id-compressor/appendOnlySortedMap.js.map +1 -0
  170. package/lib/id-compressor/idCompressor.d.ts +279 -0
  171. package/lib/id-compressor/idCompressor.d.ts.map +1 -0
  172. package/lib/id-compressor/idCompressor.js +1248 -0
  173. package/lib/id-compressor/idCompressor.js.map +1 -0
  174. package/lib/id-compressor/idRange.d.ts +11 -0
  175. package/lib/id-compressor/idRange.d.ts.map +1 -0
  176. package/lib/id-compressor/idRange.js +25 -0
  177. package/lib/id-compressor/idRange.js.map +1 -0
  178. package/lib/id-compressor/index.d.ts +14 -0
  179. package/lib/id-compressor/index.d.ts.map +1 -0
  180. package/lib/id-compressor/index.js +14 -0
  181. package/lib/id-compressor/index.js.map +1 -0
  182. package/lib/id-compressor/numericUuid.d.ts +59 -0
  183. package/lib/id-compressor/numericUuid.d.ts.map +1 -0
  184. package/lib/id-compressor/numericUuid.js +315 -0
  185. package/lib/id-compressor/numericUuid.js.map +1 -0
  186. package/lib/id-compressor/sessionIdNormalizer.d.ts +138 -0
  187. package/lib/id-compressor/sessionIdNormalizer.d.ts.map +1 -0
  188. package/lib/id-compressor/sessionIdNormalizer.js +484 -0
  189. package/lib/id-compressor/sessionIdNormalizer.js.map +1 -0
  190. package/lib/id-compressor/utils.d.ts +57 -0
  191. package/lib/id-compressor/utils.d.ts.map +1 -0
  192. package/lib/id-compressor/utils.js +79 -0
  193. package/lib/id-compressor/utils.js.map +1 -0
  194. package/lib/id-compressor/uuidUtilities.d.ts +30 -0
  195. package/lib/id-compressor/uuidUtilities.d.ts.map +1 -0
  196. package/lib/id-compressor/uuidUtilities.js +98 -0
  197. package/lib/id-compressor/uuidUtilities.js.map +1 -0
  198. package/lib/index.d.ts +1 -0
  199. package/lib/index.d.ts.map +1 -1
  200. package/lib/index.js +1 -0
  201. package/lib/index.js.map +1 -1
  202. package/lib/opLifecycle/batchManager.d.ts +9 -2
  203. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  204. package/lib/opLifecycle/batchManager.js +19 -1
  205. package/lib/opLifecycle/batchManager.js.map +1 -1
  206. package/lib/opLifecycle/index.d.ts +2 -1
  207. package/lib/opLifecycle/index.d.ts.map +1 -1
  208. package/lib/opLifecycle/index.js +1 -0
  209. package/lib/opLifecycle/index.js.map +1 -1
  210. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  211. package/lib/opLifecycle/opDecompressor.js +2 -1
  212. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  213. package/lib/opLifecycle/opGroupingManager.d.ts +14 -0
  214. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -0
  215. package/lib/opLifecycle/opGroupingManager.js +57 -0
  216. package/lib/opLifecycle/opGroupingManager.js.map +1 -0
  217. package/lib/opLifecycle/opSplitter.d.ts +1 -1
  218. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  219. package/lib/opLifecycle/opSplitter.js +5 -6
  220. package/lib/opLifecycle/opSplitter.js.map +1 -1
  221. package/lib/opLifecycle/outbox.d.ts +4 -2
  222. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  223. package/lib/opLifecycle/outbox.js +38 -26
  224. package/lib/opLifecycle/outbox.js.map +1 -1
  225. package/lib/opLifecycle/remoteMessageProcessor.d.ts +4 -2
  226. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  227. package/lib/opLifecycle/remoteMessageProcessor.js +30 -20
  228. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  229. package/lib/packageVersion.d.ts +1 -1
  230. package/lib/packageVersion.js +1 -1
  231. package/lib/packageVersion.js.map +1 -1
  232. package/lib/pendingStateManager.d.ts +1 -1
  233. package/lib/pendingStateManager.d.ts.map +1 -1
  234. package/lib/pendingStateManager.js +11 -3
  235. package/lib/pendingStateManager.js.map +1 -1
  236. package/lib/summary/index.d.ts +2 -2
  237. package/lib/summary/index.d.ts.map +1 -1
  238. package/lib/summary/index.js +2 -1
  239. package/lib/summary/index.js.map +1 -1
  240. package/lib/summary/orderedClientElection.d.ts +1 -0
  241. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  242. package/lib/summary/orderedClientElection.js +19 -0
  243. package/lib/summary/orderedClientElection.js.map +1 -1
  244. package/lib/summary/runningSummarizer.d.ts +4 -3
  245. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  246. package/lib/summary/runningSummarizer.js +65 -66
  247. package/lib/summary/runningSummarizer.js.map +1 -1
  248. package/lib/summary/summarizer.d.ts.map +1 -1
  249. package/lib/summary/summarizer.js +1 -5
  250. package/lib/summary/summarizer.js.map +1 -1
  251. package/lib/summary/summarizerHeuristics.d.ts +1 -0
  252. package/lib/summary/summarizerHeuristics.d.ts.map +1 -1
  253. package/lib/summary/summarizerHeuristics.js +3 -0
  254. package/lib/summary/summarizerHeuristics.js.map +1 -1
  255. package/lib/summary/summarizerNode/summarizerNode.js +1 -1
  256. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  257. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +128 -2
  258. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  259. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +3 -3
  260. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  261. package/lib/summary/summarizerTypes.d.ts +14 -2
  262. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  263. package/lib/summary/summarizerTypes.js.map +1 -1
  264. package/lib/summary/summaryFormat.d.ts +3 -0
  265. package/lib/summary/summaryFormat.d.ts.map +1 -1
  266. package/lib/summary/summaryFormat.js +2 -0
  267. package/lib/summary/summaryFormat.js.map +1 -1
  268. package/lib/summary/summaryGenerator.d.ts +28 -2
  269. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  270. package/lib/summary/summaryGenerator.js +17 -15
  271. package/lib/summary/summaryGenerator.js.map +1 -1
  272. package/lib/summary/summaryManager.d.ts.map +1 -1
  273. package/lib/summary/summaryManager.js +2 -0
  274. package/lib/summary/summaryManager.js.map +1 -1
  275. package/package.json +29 -17
  276. package/src/blobManager.ts +64 -41
  277. package/src/containerRuntime.ts +294 -65
  278. package/src/dataStoreContext.ts +6 -0
  279. package/src/dataStores.ts +4 -7
  280. package/src/gc/garbageCollection.ts +7 -6
  281. package/src/gc/gcConfigs.ts +1 -3
  282. package/src/gc/gcDefinitions.ts +1 -1
  283. package/src/gc/gcHelpers.ts +9 -6
  284. package/src/id-compressor/README.md +3 -0
  285. package/src/id-compressor/appendOnlySortedMap.ts +427 -0
  286. package/src/id-compressor/idCompressor.ts +1854 -0
  287. package/src/id-compressor/idRange.ts +35 -0
  288. package/src/id-compressor/index.ts +35 -0
  289. package/src/id-compressor/numericUuid.ts +383 -0
  290. package/src/id-compressor/sessionIdNormalizer.ts +609 -0
  291. package/src/id-compressor/utils.ts +114 -0
  292. package/src/id-compressor/uuidUtilities.ts +123 -0
  293. package/src/index.ts +1 -0
  294. package/src/opLifecycle/README.md +119 -0
  295. package/src/opLifecycle/batchManager.ts +35 -2
  296. package/src/opLifecycle/index.ts +2 -1
  297. package/src/opLifecycle/opDecompressor.ts +1 -0
  298. package/src/opLifecycle/opGroupingManager.ts +82 -0
  299. package/src/opLifecycle/opSplitter.ts +1 -5
  300. package/src/opLifecycle/outbox.ts +64 -26
  301. package/src/opLifecycle/remoteMessageProcessor.ts +38 -22
  302. package/src/packageVersion.ts +1 -1
  303. package/src/pendingStateManager.ts +21 -7
  304. package/src/summary/index.ts +2 -1
  305. package/src/summary/orderedClientElection.ts +17 -1
  306. package/src/summary/runningSummarizer.ts +78 -77
  307. package/src/summary/summarizer.ts +0 -8
  308. package/src/summary/summarizerHeuristics.ts +4 -0
  309. package/src/summary/summarizerNode/summarizerNode.ts +1 -1
  310. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +3 -3
  311. package/src/summary/summarizerTypes.ts +20 -3
  312. package/src/summary/summaryFormat.ts +4 -0
  313. package/src/summary/summaryGenerator.ts +22 -16
  314. package/src/summary/summaryManager.ts +2 -0
@@ -0,0 +1,114 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Remove `readonly` from all fields.
8
+ */
9
+ export type Mutable<T> = { -readonly [P in keyof T]: T[P] };
10
+
11
+ /** A union type of the first `N` positive integers */
12
+ export type TakeWholeNumbers<N extends number, A extends never[] = []> = N extends A["length"]
13
+ ? never
14
+ : A["length"] | TakeWholeNumbers<N, [never, ...A]>;
15
+
16
+ /** Returns a tuple type with exactly `Length` elements of type `T` */
17
+ export type ArrayOfLength<T, Length extends number, A extends T[] = []> = Length extends A["length"]
18
+ ? A
19
+ : ArrayOfLength<T, Length, [T, ...A]>;
20
+
21
+ /**
22
+ * Fails true iff `array` has at least `length` elements
23
+ */
24
+ export function hasAtLeastLength<T, Len extends TakeWholeNumbers<16>>(
25
+ array: readonly T[],
26
+ length: Len,
27
+ ): array is [...ArrayOfLength<T, Len>, ...T[]] {
28
+ return array.length >= length;
29
+ }
30
+
31
+ /**
32
+ * A numeric comparator used for sorting in ascending order.
33
+ *
34
+ * Handles +/-0 like Map: -0 is equal to +0.
35
+ */
36
+ export function compareFiniteNumbers<T extends number>(a: T, b: T): number {
37
+ return a - b;
38
+ }
39
+
40
+ /**
41
+ * A numeric comparator used for sorting in descending order.
42
+ *
43
+ * Handles +/-0 like Map: -0 is equal to +0.
44
+ */
45
+ export function compareFiniteNumbersReversed<T extends number>(a: T, b: T): number {
46
+ return b - a;
47
+ }
48
+
49
+ /**
50
+ * Compare two maps and return true if their contents are equivalent.
51
+ * @param mapA - The first array to compare
52
+ * @param mapB - The second array to compare
53
+ * @param elementComparator - The function used to check if two `T`s are equivalent.
54
+ * Defaults to `Object.is()` equality (a shallow compare)
55
+ */
56
+ export function compareMaps<K, V>(
57
+ mapA: ReadonlyMap<K, V>,
58
+ mapB: ReadonlyMap<K, V>,
59
+ elementComparator: (a: V, b: V) => boolean = Object.is,
60
+ ): boolean {
61
+ if (mapA.size !== mapB.size) {
62
+ return false;
63
+ }
64
+
65
+ for (const [keyA, valueA] of mapA) {
66
+ const valueB = mapB.get(keyA);
67
+ if (valueB === undefined || !elementComparator(valueA, valueB)) {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ return true;
73
+ }
74
+
75
+ /**
76
+ * Retrieve a value from a map with the given key, or create a new entry if the key is not in the map.
77
+ * @param map - The map to query/update
78
+ * @param key - The key to lookup in the map
79
+ * @param defaultValue - a function which returns a default value. This is called and used to set an initial value for the given key in the map if none exists
80
+ * @returns either the existing value for the given key, or the newly-created value (the result of `defaultValue`)
81
+ */
82
+ export function getOrCreate<K, V>(map: Map<K, V>, key: K, defaultValue: (key: K) => V): V {
83
+ let value = map.get(key);
84
+ if (value === undefined) {
85
+ value = defaultValue(key);
86
+ map.set(key, value);
87
+ }
88
+ return value;
89
+ }
90
+
91
+ /**
92
+ * Compares strings lexically to form a strict partial ordering.
93
+ */
94
+ export function compareStrings<T extends string>(a: T, b: T): number {
95
+ return a > b ? 1 : a === b ? 0 : -1;
96
+ }
97
+
98
+ export function fail(message: string): never {
99
+ throw new Error(message);
100
+ }
101
+
102
+ /**
103
+ * Sets a property in such a way that it is only set on `destination` if the provided value is not undefined.
104
+ * This avoids having explicit undefined values under properties that would cause `Object.hasOwnProperty` to return true.
105
+ */
106
+ export function setPropertyIfDefined<TDst, P extends keyof TDst>(
107
+ value: TDst[P] | undefined,
108
+ destination: TDst,
109
+ property: P,
110
+ ): void {
111
+ if (value !== undefined) {
112
+ destination[property] = value;
113
+ }
114
+ }
@@ -0,0 +1,123 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { assert } from "@fluidframework/common-utils";
7
+ import { StableId, UuidString } from "@fluidframework/runtime-definitions";
8
+ import { v4, NIL } from "uuid";
9
+
10
+ const hexadecimalCharCodes = Array.from("09afAF").map((c) => c.charCodeAt(0)) as [
11
+ zero: number,
12
+ nine: number,
13
+ a: number,
14
+ f: number,
15
+ A: number,
16
+ F: number,
17
+ ];
18
+
19
+ function isHexadecimalCharacter(charCode: number): boolean {
20
+ return (
21
+ (charCode >= hexadecimalCharCodes[0] && charCode <= hexadecimalCharCodes[1]) ||
22
+ (charCode >= hexadecimalCharCodes[2] && charCode <= hexadecimalCharCodes[3]) ||
23
+ (charCode >= hexadecimalCharCodes[4] && charCode <= hexadecimalCharCodes[5])
24
+ );
25
+ }
26
+
27
+ /** The null (lowest/all-zeros) UUID */
28
+ export const nilUuid = assertIsUuidString(NIL);
29
+
30
+ /**
31
+ * Asserts that the given string is a UUID
32
+ */
33
+ export function assertIsUuidString(uuidString: string): UuidString {
34
+ assert(isUuidString(uuidString), 0x4a2 /* Expected an UuidString */);
35
+ return uuidString;
36
+ }
37
+
38
+ /**
39
+ * Returns true iff the given string is a valid UUID-like string of hexadecimal characters
40
+ * 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
41
+ */
42
+ export function isUuidString(str: string): str is UuidString {
43
+ for (let i = 0; i < str.length; i++) {
44
+ switch (i) {
45
+ case 8:
46
+ case 13:
47
+ case 18:
48
+ case 23:
49
+ if (str.charAt(i) !== "-") {
50
+ return false;
51
+ }
52
+ break;
53
+
54
+ default:
55
+ if (!isHexadecimalCharacter(str.charCodeAt(i))) {
56
+ return false;
57
+ }
58
+ break;
59
+ }
60
+ }
61
+
62
+ return true;
63
+ }
64
+
65
+ /**
66
+ * Generate a random stable ID
67
+ */
68
+ export function generateStableId(): StableId {
69
+ return assertIsStableId(v4());
70
+ }
71
+
72
+ /**
73
+ * Asserts that the given string is a stable ID.
74
+ */
75
+ export function assertIsStableId(stableId: string): StableId {
76
+ assert(isStableId(stableId), 0x4a3 /* Expected a StableId */);
77
+ return stableId;
78
+ }
79
+
80
+ /**
81
+ * Returns true iff the given string is a valid Version 4, variant 2 UUID
82
+ * 'xxxxxxxx-xxxx-4xxx-vxxx-xxxxxxxxxxxx'
83
+ */
84
+ export function isStableId(str: string): str is StableId {
85
+ if (str.length !== 36) {
86
+ return false;
87
+ }
88
+
89
+ for (let i = 0; i < str.length; i++) {
90
+ switch (i) {
91
+ case 8:
92
+ case 13:
93
+ case 18:
94
+ case 23:
95
+ if (str.charAt(i) !== "-") {
96
+ return false;
97
+ }
98
+ break;
99
+
100
+ case 14:
101
+ if (str.charAt(i) !== "4") {
102
+ return false;
103
+ }
104
+ break;
105
+
106
+ case 19: {
107
+ const char = str.charAt(i);
108
+ if (char !== "8" && char !== "9" && char !== "a" && char !== "b") {
109
+ return false;
110
+ }
111
+ break;
112
+ }
113
+
114
+ default:
115
+ if (!isHexadecimalCharacter(str.charCodeAt(i))) {
116
+ return false;
117
+ }
118
+ break;
119
+ }
120
+ }
121
+
122
+ return true;
123
+ }
package/src/index.ts CHANGED
@@ -69,3 +69,4 @@ export {
69
69
  ICancellableSummarizerController,
70
70
  } from "./summary";
71
71
  export { IChunkedOp, unpackRuntimeMessage } from "./opLifecycle";
72
+ export { generateStableId, isStableId, assertIsStableId } from "./id-compressor";
@@ -12,10 +12,13 @@ By default, the runtime is configured with a max batch size of `716800` bytes, w
12
12
 
13
13
  - [Introduction](#introduction)
14
14
  - [Compression](#compression)
15
+ - [Grouped batching](#grouped-batching)
16
+ - [Risks](#risks)
15
17
  - [Chunking for compression](#chunking-for-compression)
16
18
  - [Disabling in case of emergency](#disabling-in-case-of-emergency)
17
19
  - [Example configs](#example-configs)
18
20
  - [How it works](#how-it-works)
21
+ - [How grouped batching works](#how-grouped-batching-works)
19
22
 
20
23
  ## Compression
21
24
 
@@ -26,6 +29,28 @@ By default, the runtime is configured with a max batch size of `716800` bytes, w
26
29
  - `minimumBatchSizeInBytes` – the minimum size of the batch for which compression should kick in. If the payload is too small, compression may not yield too many benefits. To target the original 1MB issue, a good value here would be to match the default maxBatchSizeInBytes (972800), however, experimentally, a good lower value could be at around 614400 bytes. Setting this value to `Number.POSITIVE_INFINITY` will disable compression.
27
30
  - `compressionAlgorithm` – currently, only `lz4` is supported.
28
31
 
32
+ ## Grouped batching
33
+
34
+ **Note: This feature is currently considered experimental and is not ready for production usage.**
35
+
36
+ The `IContainerRuntimeOptions.enableGroupedBatching` option has been added to the container runtime layer and is **off by default**. This option will group all batch messages under a new "grouped" message to be sent to the service. Upon receiving this new "grouped" message, the batch messages will be extracted and given the sequence number of the parent "grouped" message.
37
+
38
+ The purpose for enabling grouped batching on top of compression is that regular compression won't include the empty messages in the chunks. Thus, if we have batches with many messages (i.e. more than 4k), we will go over the batch size limit just on empty op envelopes alone.
39
+
40
+ See [below](#how-grouped-batching-works) for an example.
41
+
42
+ ### Risks
43
+
44
+ This option is experimental and should not be enabled yet in production. This option should **ONLY** be enabled after observing that 99.9% of your application sessions contains these changes (runtime version "2.0.0-internal.4.1.0" or later). Containers created with this option may not open in future versions of the framework.
45
+
46
+ This option will change a couple of expectations around message structure and runtime layer expectations. Only enable this option after testing
47
+ and verifying that the following expectation changes won't have any effects:
48
+
49
+ - batch messages observed at the runtime layer will not match messages seen at the loader layer (i.e. grouped form at loader layer, ungrouped form at runtime layer)
50
+ - messages within the same batch will have the same sequence number
51
+ - client sequence numbers on batch messages can only be used to order messages with the same sequenceNumber
52
+ - requires all ops to be processed by runtime layer (version "2.0.0-internal.1.2.0" or later https://github.com/microsoft/FluidFramework/pull/11832)
53
+
29
54
  ## Chunking for compression
30
55
 
31
56
  **Op chunking for compression targets payloads which exceed the max batch size after compression.** So, only payloads which are already compressed. By default, the feature is enabled.
@@ -39,6 +64,7 @@ This config would govern chunking compressed batches only. We will not be enabli
39
64
  If the features are enabled using the configs, they can be disabled at runtime via feature gates as following:
40
65
 
41
66
  - `Fluid.ContainerRuntime.CompressionDisabled` - if set to true, will disable compression (this has a side effect of also disabling chunking, as chunking is invoked only for compressed payloads).
67
+ - `Fluid.ContainerRuntime.DisableGroupedBatching` - if set to true, will disable grouped batching.
42
68
  - `Fluid.ContainerRuntime.CompressionChunkingDisabled` - if set to true, will disable chunking for compression.
43
69
 
44
70
  ## Example configs
@@ -75,6 +101,14 @@ To disable compression (will also disable chunking, as chunking works only for c
75
101
      }
76
102
  ```
77
103
 
104
+ To enable grouped batching:
105
+
106
+ ```
107
+ const runtimeOptions: IContainerRuntimeOptions = {
108
+ enableGroupedBatching: true,
109
+     }
110
+ ```
111
+
78
112
  ## How it works
79
113
 
80
114
  Compression currently works as a runtime layer over the regular op sending/receiving pipeline.
@@ -155,3 +189,88 @@ Notice that the sequence numbers don’t matter here, as all ops will be based o
155
189
  Additionally, as compression preserves the original uncompressed batch layout in terms of the number of ops by using empty ops to reserve the sequence numbers, this ensures that the clients will always receive the exact count of ops to rebuild the uncompressed batch sequentially.
156
190
 
157
191
  On the receiving end, the client will accumulate chunks 1 and 2 and keep them in memory. When chunk 3 is received, the original large, decompressed op will be rebuilt, and the runtime will then process the batch as if it is a compressed batch.
192
+
193
+ ## How grouped batching works
194
+
195
+ **Note: There are plans to replace empty ops with something more efficient when doing grouped batching AB#4092**
196
+
197
+ Given the following baseline batch:
198
+
199
+ ```
200
+ +---------------+---------------+---------------+---------------+---------------+
201
+ | Op 1 | Op 2 | Op 3 | Op 4 | Op 5 |
202
+ | Contents: "a" | Contents: "b" | Contents: "c" | Contents: "d" | Contents: "e" |
203
+ +---------------+---------------+---------------+---------------+---------------+
204
+ ```
205
+
206
+ Compressed batch:
207
+
208
+ ```
209
+ +--------------------+-----------------+-----------------+-----------------+-----------------+
210
+ | Op 1 | Op 2 | Op 3 | Op 4 | Op 5 |
211
+ | Contents: "abcde" | Contents: empty | Contents: empty | Contents: empty | Contents: empty |
212
+ | Compression: 'lz4' | | | | |
213
+ +--------------------+-----------------+-----------------+-----------------+-----------------+
214
+ ```
215
+
216
+ Grouped batch:
217
+
218
+ ```
219
+ +---------------------------------------------------------------------------------------------------------------------------------+
220
+ | Op 1 Contents: +--------------------+-----------------+-----------------+-----------------+-----------------+ |
221
+ | SeqNum: 1 | Op 1 | Op 2 | Op 3 | Op 4 | Op 5 | |
222
+ | Type: "groupedBatch" | Contents: "abcde" | Contents: empty | Contents: empty | Contents: empty | Contents: empty | |
223
+ | | Compression: 'lz4' | | | | | |
224
+ | +--------------------+-----------------+-----------------+-----------------+-----------------+ |
225
+ +---------------------------------------------------------------------------------------------------------------------------------+
226
+ ```
227
+
228
+ Can produce the following chunks:
229
+
230
+ ```
231
+ +-------------------------------------------------+
232
+ | Chunk 1/2 Contents: +----------------------+ |
233
+ | SeqNum: 1 | +-----------------+ | |
234
+ | | | Contents: "abc" | | |
235
+ | | +-----------------+ | |
236
+ | +----------------------+ |
237
+ +-------------------------------------------------+
238
+ ```
239
+
240
+ ```
241
+ +--------------------------------------------------------------------------------------------------------------------------+
242
+ | Chunk 2/2 Contents: +---------------------------------------------------------------------------------------------+ | |
243
+ | SeqNum: 2 | +----------------+-----------------+-----------------+-----------------+-----------------+ | | |
244
+ | | | Contents: "de" | Contents: empty | Contents: empty | Contents: empty | Contents: empty | | | |
245
+ | | +----------------+-----------------+-----------------+-----------------+-----------------+ | | |
246
+ | +---------------------------------------------------------------------------------------------+ | |
247
+ +--------------------------------------------------------------------------------------------------------------------------+
248
+ ```
249
+
250
+ - Send to service
251
+ - Service acks ops sent
252
+ - Receive chunks from service
253
+ - Recompile to the grouped batch step
254
+
255
+ Ungrouped batch:
256
+
257
+ ```
258
+ +--------------------+-----------------+-----------------+-----------------+-----------------+
259
+ | Op 1 | Op 2 | Op 3 | Op 4 | Op 5 |
260
+ | Contents: "abcde" | Contents: empty | Contents: empty | Contents: empty | Contents: empty |
261
+ | SeqNum: 2 | SeqNum: 2 | SeqNum: 2 | SeqNum: 2 | SeqNum: 2 |
262
+ | ClientSeqNum: 1 | ClientSeqNum: 2 | ClientSeqNum: 3 | ClientSeqNum: 4 | ClientSeqNum: 5 |
263
+ | Compression: 'lz4' | | | | |
264
+ +--------------------+-----------------+-----------------+-----------------+-----------------+
265
+ ```
266
+
267
+ Uncompressed batch:
268
+
269
+ ```
270
+ +-----------------+-----------------+-----------------+-----------------+-----------------+
271
+ | Op 1 | Op 2 | Op 3 | Op 4 | Op 5 |
272
+ | Contents: "a" | Contents: "b" | Contents: "c" | Contents: "d" | Contents: "e" |
273
+ | SeqNum: 2 | SeqNum: 2 | SeqNum: 2 | SeqNum: 2 | SeqNum: 2 |
274
+ | ClientSeqNum: 1 | ClientSeqNum: 2 | ClientSeqNum: 3 | ClientSeqNum: 4 | ClientSeqNum: 5 |
275
+ +-----------------+-----------------+-----------------+-----------------+-----------------+
276
+ ```
@@ -12,6 +12,11 @@ export interface IBatchManagerOptions {
12
12
  readonly compressionOptions?: ICompressionRuntimeOptions;
13
13
  }
14
14
 
15
+ export interface BatchSequenceNumbers {
16
+ referenceSequenceNumber?: number;
17
+ clientSequenceNumber?: number;
18
+ }
19
+
15
20
  /**
16
21
  * Estimated size of the stringification overhead for an op accumulated
17
22
  * from runtime to loader to the service.
@@ -32,15 +37,24 @@ export class BatchManager {
32
37
  return this.batchContentSize;
33
38
  }
34
39
 
35
- public get referenceSequenceNumber(): number | undefined {
40
+ public get sequenceNumbers(): BatchSequenceNumbers {
41
+ return {
42
+ referenceSequenceNumber: this.referenceSequenceNumber,
43
+ clientSequenceNumber: this.clientSequenceNumber,
44
+ };
45
+ }
46
+
47
+ private get referenceSequenceNumber(): number | undefined {
36
48
  return this.pendingBatch.length === 0
37
49
  ? undefined
38
50
  : this.pendingBatch[this.pendingBatch.length - 1].referenceSequenceNumber;
39
51
  }
40
52
 
53
+ private clientSequenceNumber: number | undefined;
54
+
41
55
  constructor(public readonly options: IBatchManagerOptions) {}
42
56
 
43
- public push(message: BatchMessage): boolean {
57
+ public push(message: BatchMessage, currentClientSequenceNumber?: number): boolean {
44
58
  const contentSize = this.batchContentSize + (message.contents?.length ?? 0);
45
59
  const opCount = this.pendingBatch.length;
46
60
 
@@ -68,6 +82,10 @@ export class BatchManager {
68
82
  return false;
69
83
  }
70
84
 
85
+ if (this.pendingBatch.length === 0) {
86
+ this.clientSequenceNumber = currentClientSequenceNumber;
87
+ }
88
+
71
89
  this.batchContentSize = contentSize;
72
90
  this.pendingBatch.push(message);
73
91
  return true;
@@ -86,6 +104,7 @@ export class BatchManager {
86
104
 
87
105
  this.pendingBatch = [];
88
106
  this.batchContentSize = 0;
107
+ this.clientSequenceNumber = undefined;
89
108
 
90
109
  return addBatchMetadata(batch);
91
110
  }
@@ -136,3 +155,17 @@ const addBatchMetadata = (batch: IBatch): IBatch => {
136
155
  export const estimateSocketSize = (batch: IBatch): number => {
137
156
  return batch.contentSizeInBytes + opOverhead * batch.content.length;
138
157
  };
158
+
159
+ export const sequenceNumbersMatch = (
160
+ seqNums: BatchSequenceNumbers,
161
+ otherSeqNums: BatchSequenceNumbers,
162
+ ): boolean => {
163
+ return (
164
+ (seqNums.referenceSequenceNumber === undefined ||
165
+ otherSeqNums.referenceSequenceNumber === undefined ||
166
+ seqNums.referenceSequenceNumber === otherSeqNums.referenceSequenceNumber) &&
167
+ (seqNums.clientSequenceNumber === undefined ||
168
+ otherSeqNums.clientSequenceNumber === undefined ||
169
+ seqNums.clientSequenceNumber === otherSeqNums.clientSequenceNumber)
170
+ );
171
+ };
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- export { BatchManager, estimateSocketSize } from "./batchManager";
6
+ export { BatchManager, estimateSocketSize, BatchSequenceNumbers } from "./batchManager";
7
7
  export {
8
8
  BatchMessage,
9
9
  IBatch,
@@ -16,3 +16,4 @@ export { OpCompressor } from "./opCompressor";
16
16
  export { OpDecompressor } from "./opDecompressor";
17
17
  export { OpSplitter, splitOp } from "./opSplitter";
18
18
  export { RemoteMessageProcessor, unpackRuntimeMessage } from "./remoteMessageProcessor";
19
+ export { OpGroupingManager } from "./opGroupingManager";
@@ -133,6 +133,7 @@ export class OpDecompressor {
133
133
  */
134
134
  try {
135
135
  if (
136
+ message.contents !== null &&
136
137
  typeof message.contents === "object" &&
137
138
  Object.keys(message.contents).length === 1 &&
138
139
  message.contents?.packedContents !== undefined &&
@@ -0,0 +1,82 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { assert } from "@fluidframework/common-utils";
7
+ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
+ import { ContainerMessageType, ContainerRuntimeMessage } from "..";
9
+ import { IBatch } from "./definitions";
10
+
11
+ interface IGroupedMessage {
12
+ contents?: unknown;
13
+ metadata?: Record<string, unknown>;
14
+ compression?: string;
15
+ }
16
+
17
+ export class OpGroupingManager {
18
+ static groupedBatchOp = "groupedBatch";
19
+
20
+ constructor(private readonly groupedBatchingEnabled: boolean) {}
21
+
22
+ public groupBatch(batch: IBatch): IBatch {
23
+ if (batch.content.length < 2 || !this.groupedBatchingEnabled) {
24
+ return batch;
25
+ }
26
+
27
+ for (const message of batch.content) {
28
+ // Blob attaches cannot be grouped (grouped batching would hide metadata)
29
+ if (message.deserializedContent.type === ContainerMessageType.BlobAttach) {
30
+ return batch;
31
+ }
32
+ if (message.metadata) {
33
+ const keys = Object.keys(message.metadata);
34
+ assert(keys.length < 2, 0x5dd /* cannot group ops with metadata */);
35
+ assert(
36
+ keys.length === 0 || keys[0] === "batch",
37
+ 0x5de /* unexpected op metadata */,
38
+ );
39
+ }
40
+ }
41
+
42
+ // Need deserializedContent for back-compat
43
+ const deserializedContent = {
44
+ type: OpGroupingManager.groupedBatchOp,
45
+ contents: batch.content.map<IGroupedMessage>((message) => ({
46
+ contents: message.contents === undefined ? undefined : JSON.parse(message.contents),
47
+ metadata: message.metadata,
48
+ compression: message.compression,
49
+ })),
50
+ };
51
+
52
+ const groupedBatch: IBatch = {
53
+ ...batch,
54
+ content: [
55
+ {
56
+ localOpMetadata: undefined,
57
+ metadata: undefined,
58
+ referenceSequenceNumber: batch.content[0].referenceSequenceNumber,
59
+ deserializedContent: deserializedContent as ContainerRuntimeMessage,
60
+ contents: JSON.stringify(deserializedContent),
61
+ },
62
+ ],
63
+ };
64
+ return groupedBatch;
65
+ }
66
+
67
+ public ungroupOp(op: ISequencedDocumentMessage): ISequencedDocumentMessage[] {
68
+ if (op.contents?.type !== OpGroupingManager.groupedBatchOp) {
69
+ return [op];
70
+ }
71
+
72
+ const messages = op.contents.contents as IGroupedMessage[];
73
+ let fakeCsn = 1;
74
+ return messages.map((subMessage) => ({
75
+ ...op,
76
+ clientSequenceNumber: fakeCsn++,
77
+ contents: subMessage.contents,
78
+ metadata: subMessage.metadata,
79
+ compression: subMessage.compression,
80
+ }));
81
+ }
82
+ }
@@ -135,7 +135,7 @@ export class OpSplitter {
135
135
  * @param batch - the compressed batch which needs to be processed
136
136
  * @returns A new adjusted batch which can be sent over the wire
137
137
  */
138
- public splitCompressedBatch(batch: IBatch): IBatch {
138
+ public splitFirstBatchMessage(batch: IBatch): IBatch {
139
139
  assert(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);
140
140
  assert(
141
141
  batch.contentSizeInBytes > 0 && batch.content.length > 0,
@@ -152,10 +152,6 @@ export class OpSplitter {
152
152
  );
153
153
 
154
154
  const firstMessage = batch.content[0]; // we expect this to be the large compressed op, which needs to be split
155
- assert(
156
- firstMessage.metadata?.compressed === true || firstMessage.compression !== undefined,
157
- 0x517 /* Batch needs to be compressed */,
158
- );
159
155
  assert(
160
156
  (firstMessage.contents?.length ?? 0) >= this.chunkSizeInBytes,
161
157
  0x518 /* First message in the batch needs to be chunkable */,