@fluidframework/container-runtime 2.0.0-dev.2.2.0.111723 → 2.0.0-dev.3.1.0.125672

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 (365) hide show
  1. package/.eslintrc.js +21 -10
  2. package/.mocharc.js +2 -2
  3. package/api-extractor.json +2 -2
  4. package/dist/batchTracker.d.ts +1 -2
  5. package/dist/batchTracker.d.ts.map +1 -1
  6. package/dist/batchTracker.js +2 -1
  7. package/dist/batchTracker.js.map +1 -1
  8. package/dist/blobManager.d.ts +62 -28
  9. package/dist/blobManager.d.ts.map +1 -1
  10. package/dist/blobManager.js +256 -102
  11. package/dist/blobManager.js.map +1 -1
  12. package/dist/connectionTelemetry.d.ts.map +1 -1
  13. package/dist/connectionTelemetry.js +11 -9
  14. package/dist/connectionTelemetry.js.map +1 -1
  15. package/dist/containerHandleContext.d.ts.map +1 -1
  16. package/dist/containerHandleContext.js +3 -1
  17. package/dist/containerHandleContext.js.map +1 -1
  18. package/dist/containerRuntime.d.ts +110 -78
  19. package/dist/containerRuntime.d.ts.map +1 -1
  20. package/dist/containerRuntime.js +336 -331
  21. package/dist/containerRuntime.js.map +1 -1
  22. package/dist/dataStore.d.ts.map +1 -1
  23. package/dist/dataStore.js +11 -9
  24. package/dist/dataStore.js.map +1 -1
  25. package/dist/dataStoreContext.d.ts +2 -1
  26. package/dist/dataStoreContext.d.ts.map +1 -1
  27. package/dist/dataStoreContext.js +40 -23
  28. package/dist/dataStoreContext.js.map +1 -1
  29. package/dist/dataStoreContexts.d.ts.map +1 -1
  30. package/dist/dataStoreContexts.js +7 -3
  31. package/dist/dataStoreContexts.js.map +1 -1
  32. package/dist/dataStoreRegistry.d.ts.map +1 -1
  33. package/dist/dataStoreRegistry.js +3 -1
  34. package/dist/dataStoreRegistry.js.map +1 -1
  35. package/dist/dataStores.d.ts +12 -9
  36. package/dist/dataStores.d.ts.map +1 -1
  37. package/dist/dataStores.js +69 -46
  38. package/dist/dataStores.js.map +1 -1
  39. package/dist/deltaScheduler.d.ts.map +1 -1
  40. package/dist/deltaScheduler.js +8 -3
  41. package/dist/deltaScheduler.js.map +1 -1
  42. package/dist/garbageCollection.d.ts +57 -42
  43. package/dist/garbageCollection.d.ts.map +1 -1
  44. package/dist/garbageCollection.js +371 -239
  45. package/dist/garbageCollection.js.map +1 -1
  46. package/dist/garbageCollectionConstants.d.ts +23 -0
  47. package/dist/garbageCollectionConstants.d.ts.map +1 -0
  48. package/dist/garbageCollectionConstants.js +36 -0
  49. package/dist/garbageCollectionConstants.js.map +1 -0
  50. package/dist/garbageCollectionHelpers.d.ts +15 -0
  51. package/dist/garbageCollectionHelpers.d.ts.map +1 -0
  52. package/dist/garbageCollectionHelpers.js +27 -0
  53. package/dist/garbageCollectionHelpers.js.map +1 -0
  54. package/dist/gcSweepReadyUsageDetection.d.ts +5 -5
  55. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  56. package/dist/gcSweepReadyUsageDetection.js +15 -11
  57. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  58. package/dist/index.d.ts +4 -3
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +5 -6
  61. package/dist/index.js.map +1 -1
  62. package/dist/opLifecycle/batchManager.d.ts +42 -0
  63. package/dist/opLifecycle/batchManager.d.ts.map +1 -0
  64. package/dist/opLifecycle/batchManager.js +124 -0
  65. package/dist/opLifecycle/batchManager.js.map +1 -0
  66. package/dist/opLifecycle/definitions.d.ts +64 -0
  67. package/dist/opLifecycle/definitions.d.ts.map +1 -0
  68. package/dist/opLifecycle/definitions.js +7 -0
  69. package/dist/opLifecycle/definitions.js.map +1 -0
  70. package/dist/opLifecycle/index.d.ts +12 -0
  71. package/dist/opLifecycle/index.d.ts.map +1 -0
  72. package/dist/opLifecycle/index.js +22 -0
  73. package/dist/opLifecycle/index.js.map +1 -0
  74. package/dist/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +3 -3
  75. package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
  76. package/dist/opLifecycle/opCompressor.js +67 -0
  77. package/dist/opLifecycle/opCompressor.js.map +1 -0
  78. package/dist/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +2 -1
  79. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
  80. package/dist/{opDecompressor.js → opLifecycle/opDecompressor.js} +37 -21
  81. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  82. package/dist/opLifecycle/opSplitter.d.ts +49 -0
  83. package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
  84. package/dist/opLifecycle/opSplitter.js +173 -0
  85. package/dist/opLifecycle/opSplitter.js.map +1 -0
  86. package/dist/opLifecycle/outbox.d.ts +52 -0
  87. package/dist/opLifecycle/outbox.d.ts.map +1 -0
  88. package/dist/opLifecycle/outbox.js +164 -0
  89. package/dist/opLifecycle/outbox.js.map +1 -0
  90. package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  91. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  92. package/dist/opLifecycle/remoteMessageProcessor.js +96 -0
  93. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  94. package/dist/opProperties.d.ts.map +1 -1
  95. package/dist/opProperties.js +1 -3
  96. package/dist/opProperties.js.map +1 -1
  97. package/dist/orderedClientElection.d.ts.map +1 -1
  98. package/dist/orderedClientElection.js +10 -4
  99. package/dist/orderedClientElection.js.map +1 -1
  100. package/dist/packageVersion.d.ts +1 -1
  101. package/dist/packageVersion.js +1 -1
  102. package/dist/packageVersion.js.map +1 -1
  103. package/dist/pendingStateManager.d.ts +4 -13
  104. package/dist/pendingStateManager.d.ts.map +1 -1
  105. package/dist/pendingStateManager.js +134 -161
  106. package/dist/pendingStateManager.js.map +1 -1
  107. package/dist/runWhileConnectedCoordinator.d.ts.map +1 -1
  108. package/dist/runWhileConnectedCoordinator.js.map +1 -1
  109. package/dist/runningSummarizer.d.ts.map +1 -1
  110. package/dist/runningSummarizer.js +34 -22
  111. package/dist/runningSummarizer.js.map +1 -1
  112. package/dist/scheduleManager.d.ts +0 -1
  113. package/dist/scheduleManager.d.ts.map +1 -1
  114. package/dist/scheduleManager.js +11 -21
  115. package/dist/scheduleManager.js.map +1 -1
  116. package/dist/serializedSnapshotStorage.d.ts.map +1 -1
  117. package/dist/serializedSnapshotStorage.js +3 -1
  118. package/dist/serializedSnapshotStorage.js.map +1 -1
  119. package/dist/summarizer.d.ts +2 -3
  120. package/dist/summarizer.d.ts.map +1 -1
  121. package/dist/summarizer.js +39 -18
  122. package/dist/summarizer.js.map +1 -1
  123. package/dist/summarizerClientElection.d.ts +1 -2
  124. package/dist/summarizerClientElection.d.ts.map +1 -1
  125. package/dist/summarizerClientElection.js +3 -30
  126. package/dist/summarizerClientElection.js.map +1 -1
  127. package/dist/summarizerHandle.d.ts.map +1 -1
  128. package/dist/summarizerHandle.js.map +1 -1
  129. package/dist/summarizerHeuristics.d.ts.map +1 -1
  130. package/dist/summarizerHeuristics.js +6 -9
  131. package/dist/summarizerHeuristics.js.map +1 -1
  132. package/dist/summarizerTypes.d.ts +22 -25
  133. package/dist/summarizerTypes.d.ts.map +1 -1
  134. package/dist/summarizerTypes.js.map +1 -1
  135. package/dist/summaryCollection.d.ts.map +1 -1
  136. package/dist/summaryCollection.js +18 -8
  137. package/dist/summaryCollection.js.map +1 -1
  138. package/dist/summaryFormat.d.ts.map +1 -1
  139. package/dist/summaryFormat.js +18 -11
  140. package/dist/summaryFormat.js.map +1 -1
  141. package/dist/summaryGenerator.d.ts.map +1 -1
  142. package/dist/summaryGenerator.js +32 -14
  143. package/dist/summaryGenerator.js.map +1 -1
  144. package/dist/summaryManager.d.ts.map +1 -1
  145. package/dist/summaryManager.js +21 -9
  146. package/dist/summaryManager.js.map +1 -1
  147. package/dist/throttler.d.ts +2 -2
  148. package/dist/throttler.d.ts.map +1 -1
  149. package/dist/throttler.js +4 -4
  150. package/dist/throttler.js.map +1 -1
  151. package/garbageCollection.md +15 -2
  152. package/lib/batchTracker.d.ts +1 -2
  153. package/lib/batchTracker.d.ts.map +1 -1
  154. package/lib/batchTracker.js +2 -1
  155. package/lib/batchTracker.js.map +1 -1
  156. package/lib/blobManager.d.ts +62 -28
  157. package/lib/blobManager.d.ts.map +1 -1
  158. package/lib/blobManager.js +259 -105
  159. package/lib/blobManager.js.map +1 -1
  160. package/lib/connectionTelemetry.d.ts.map +1 -1
  161. package/lib/connectionTelemetry.js +11 -9
  162. package/lib/connectionTelemetry.js.map +1 -1
  163. package/lib/containerHandleContext.d.ts.map +1 -1
  164. package/lib/containerHandleContext.js +3 -1
  165. package/lib/containerHandleContext.js.map +1 -1
  166. package/lib/containerRuntime.d.ts +110 -78
  167. package/lib/containerRuntime.d.ts.map +1 -1
  168. package/lib/containerRuntime.js +340 -334
  169. package/lib/containerRuntime.js.map +1 -1
  170. package/lib/dataStore.d.ts.map +1 -1
  171. package/lib/dataStore.js +11 -9
  172. package/lib/dataStore.js.map +1 -1
  173. package/lib/dataStoreContext.d.ts +2 -1
  174. package/lib/dataStoreContext.d.ts.map +1 -1
  175. package/lib/dataStoreContext.js +41 -24
  176. package/lib/dataStoreContext.js.map +1 -1
  177. package/lib/dataStoreContexts.d.ts.map +1 -1
  178. package/lib/dataStoreContexts.js +7 -3
  179. package/lib/dataStoreContexts.js.map +1 -1
  180. package/lib/dataStoreRegistry.d.ts.map +1 -1
  181. package/lib/dataStoreRegistry.js +3 -1
  182. package/lib/dataStoreRegistry.js.map +1 -1
  183. package/lib/dataStores.d.ts +12 -9
  184. package/lib/dataStores.d.ts.map +1 -1
  185. package/lib/dataStores.js +75 -52
  186. package/lib/dataStores.js.map +1 -1
  187. package/lib/deltaScheduler.d.ts.map +1 -1
  188. package/lib/deltaScheduler.js +9 -4
  189. package/lib/deltaScheduler.js.map +1 -1
  190. package/lib/garbageCollection.d.ts +57 -42
  191. package/lib/garbageCollection.d.ts.map +1 -1
  192. package/lib/garbageCollection.js +364 -232
  193. package/lib/garbageCollection.js.map +1 -1
  194. package/lib/garbageCollectionConstants.d.ts +23 -0
  195. package/lib/garbageCollectionConstants.d.ts.map +1 -0
  196. package/lib/garbageCollectionConstants.js +33 -0
  197. package/lib/garbageCollectionConstants.js.map +1 -0
  198. package/lib/garbageCollectionHelpers.d.ts +15 -0
  199. package/lib/garbageCollectionHelpers.d.ts.map +1 -0
  200. package/lib/garbageCollectionHelpers.js +23 -0
  201. package/lib/garbageCollectionHelpers.js.map +1 -0
  202. package/lib/gcSweepReadyUsageDetection.d.ts +5 -5
  203. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  204. package/lib/gcSweepReadyUsageDetection.js +15 -11
  205. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  206. package/lib/index.d.ts +4 -3
  207. package/lib/index.d.ts.map +1 -1
  208. package/lib/index.js +3 -3
  209. package/lib/index.js.map +1 -1
  210. package/lib/opLifecycle/batchManager.d.ts +42 -0
  211. package/lib/opLifecycle/batchManager.d.ts.map +1 -0
  212. package/lib/opLifecycle/batchManager.js +120 -0
  213. package/lib/opLifecycle/batchManager.js.map +1 -0
  214. package/lib/opLifecycle/definitions.d.ts +64 -0
  215. package/lib/opLifecycle/definitions.d.ts.map +1 -0
  216. package/lib/opLifecycle/definitions.js +6 -0
  217. package/lib/opLifecycle/definitions.js.map +1 -0
  218. package/lib/opLifecycle/index.d.ts +12 -0
  219. package/lib/opLifecycle/index.d.ts.map +1 -0
  220. package/lib/opLifecycle/index.js +11 -0
  221. package/lib/opLifecycle/index.js.map +1 -0
  222. package/lib/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +3 -3
  223. package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
  224. package/lib/opLifecycle/opCompressor.js +63 -0
  225. package/lib/opLifecycle/opCompressor.js.map +1 -0
  226. package/lib/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +2 -1
  227. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
  228. package/lib/{opDecompressor.js → opLifecycle/opDecompressor.js} +37 -21
  229. package/lib/opLifecycle/opDecompressor.js.map +1 -0
  230. package/lib/opLifecycle/opSplitter.d.ts +49 -0
  231. package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
  232. package/lib/opLifecycle/opSplitter.js +168 -0
  233. package/lib/opLifecycle/opSplitter.js.map +1 -0
  234. package/lib/opLifecycle/outbox.d.ts +52 -0
  235. package/lib/opLifecycle/outbox.d.ts.map +1 -0
  236. package/lib/opLifecycle/outbox.js +160 -0
  237. package/lib/opLifecycle/outbox.js.map +1 -0
  238. package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
  239. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
  240. package/lib/opLifecycle/remoteMessageProcessor.js +91 -0
  241. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
  242. package/lib/opProperties.d.ts.map +1 -1
  243. package/lib/opProperties.js +1 -3
  244. package/lib/opProperties.js.map +1 -1
  245. package/lib/orderedClientElection.d.ts.map +1 -1
  246. package/lib/orderedClientElection.js +10 -4
  247. package/lib/orderedClientElection.js.map +1 -1
  248. package/lib/packageVersion.d.ts +1 -1
  249. package/lib/packageVersion.js +1 -1
  250. package/lib/packageVersion.js.map +1 -1
  251. package/lib/pendingStateManager.d.ts +4 -13
  252. package/lib/pendingStateManager.d.ts.map +1 -1
  253. package/lib/pendingStateManager.js +134 -161
  254. package/lib/pendingStateManager.js.map +1 -1
  255. package/lib/runWhileConnectedCoordinator.d.ts.map +1 -1
  256. package/lib/runWhileConnectedCoordinator.js.map +1 -1
  257. package/lib/runningSummarizer.d.ts.map +1 -1
  258. package/lib/runningSummarizer.js +35 -23
  259. package/lib/runningSummarizer.js.map +1 -1
  260. package/lib/scheduleManager.d.ts +0 -1
  261. package/lib/scheduleManager.d.ts.map +1 -1
  262. package/lib/scheduleManager.js +11 -21
  263. package/lib/scheduleManager.js.map +1 -1
  264. package/lib/serializedSnapshotStorage.d.ts.map +1 -1
  265. package/lib/serializedSnapshotStorage.js +3 -1
  266. package/lib/serializedSnapshotStorage.js.map +1 -1
  267. package/lib/summarizer.d.ts +2 -3
  268. package/lib/summarizer.d.ts.map +1 -1
  269. package/lib/summarizer.js +39 -18
  270. package/lib/summarizer.js.map +1 -1
  271. package/lib/summarizerClientElection.d.ts +1 -2
  272. package/lib/summarizerClientElection.d.ts.map +1 -1
  273. package/lib/summarizerClientElection.js +3 -30
  274. package/lib/summarizerClientElection.js.map +1 -1
  275. package/lib/summarizerHandle.d.ts.map +1 -1
  276. package/lib/summarizerHandle.js.map +1 -1
  277. package/lib/summarizerHeuristics.d.ts.map +1 -1
  278. package/lib/summarizerHeuristics.js +6 -9
  279. package/lib/summarizerHeuristics.js.map +1 -1
  280. package/lib/summarizerTypes.d.ts +22 -25
  281. package/lib/summarizerTypes.d.ts.map +1 -1
  282. package/lib/summarizerTypes.js.map +1 -1
  283. package/lib/summaryCollection.d.ts.map +1 -1
  284. package/lib/summaryCollection.js +18 -8
  285. package/lib/summaryCollection.js.map +1 -1
  286. package/lib/summaryFormat.d.ts.map +1 -1
  287. package/lib/summaryFormat.js +20 -13
  288. package/lib/summaryFormat.js.map +1 -1
  289. package/lib/summaryGenerator.d.ts.map +1 -1
  290. package/lib/summaryGenerator.js +32 -14
  291. package/lib/summaryGenerator.js.map +1 -1
  292. package/lib/summaryManager.d.ts.map +1 -1
  293. package/lib/summaryManager.js +21 -9
  294. package/lib/summaryManager.js.map +1 -1
  295. package/lib/throttler.d.ts +2 -2
  296. package/lib/throttler.d.ts.map +1 -1
  297. package/lib/throttler.js +4 -4
  298. package/lib/throttler.js.map +1 -1
  299. package/package.json +28 -38
  300. package/prettier.config.cjs +1 -1
  301. package/src/batchTracker.ts +55 -50
  302. package/src/blobManager.ts +802 -541
  303. package/src/connectionTelemetry.ts +280 -249
  304. package/src/containerHandleContext.ts +27 -29
  305. package/src/containerRuntime.ts +3125 -2982
  306. package/src/dataStore.ts +172 -159
  307. package/src/dataStoreContext.ts +1049 -992
  308. package/src/dataStoreContexts.ts +178 -161
  309. package/src/dataStoreRegistry.ts +25 -20
  310. package/src/dataStores.ts +785 -711
  311. package/src/deltaScheduler.ts +158 -150
  312. package/src/garbageCollection.ts +1797 -1558
  313. package/src/garbageCollectionConstants.ts +38 -0
  314. package/src/garbageCollectionHelpers.ts +37 -0
  315. package/src/gcSweepReadyUsageDetection.ts +90 -84
  316. package/src/index.ts +68 -69
  317. package/src/opLifecycle/batchManager.ts +167 -0
  318. package/src/opLifecycle/definitions.ts +70 -0
  319. package/src/opLifecycle/index.ts +18 -0
  320. package/src/opLifecycle/opCompressor.ts +82 -0
  321. package/src/opLifecycle/opDecompressor.ts +124 -0
  322. package/src/opLifecycle/opSplitter.ts +238 -0
  323. package/src/opLifecycle/outbox.ts +228 -0
  324. package/src/opLifecycle/remoteMessageProcessor.ts +106 -0
  325. package/src/opProperties.ts +11 -9
  326. package/src/orderedClientElection.ts +489 -457
  327. package/src/packageVersion.ts +1 -1
  328. package/src/pendingStateManager.ts +379 -381
  329. package/src/runWhileConnectedCoordinator.ts +78 -71
  330. package/src/runningSummarizer.ts +619 -582
  331. package/src/scheduleManager.ts +299 -280
  332. package/src/serializedSnapshotStorage.ts +116 -111
  333. package/src/summarizer.ts +417 -381
  334. package/src/summarizerClientElection.ts +107 -129
  335. package/src/summarizerHandle.ts +11 -9
  336. package/src/summarizerHeuristics.ts +183 -186
  337. package/src/summarizerTypes.ts +344 -333
  338. package/src/summaryCollection.ts +378 -349
  339. package/src/summaryFormat.ts +146 -127
  340. package/src/summaryGenerator.ts +464 -406
  341. package/src/summaryManager.ts +377 -348
  342. package/src/throttler.ts +131 -122
  343. package/tsconfig.esnext.json +6 -6
  344. package/tsconfig.json +9 -13
  345. package/dist/batchManager.d.ts +0 -42
  346. package/dist/batchManager.d.ts.map +0 -1
  347. package/dist/batchManager.js +0 -83
  348. package/dist/batchManager.js.map +0 -1
  349. package/dist/opCompressor.d.ts.map +0 -1
  350. package/dist/opCompressor.js +0 -50
  351. package/dist/opCompressor.js.map +0 -1
  352. package/dist/opDecompressor.d.ts.map +0 -1
  353. package/dist/opDecompressor.js.map +0 -1
  354. package/lib/batchManager.d.ts +0 -42
  355. package/lib/batchManager.d.ts.map +0 -1
  356. package/lib/batchManager.js +0 -79
  357. package/lib/batchManager.js.map +0 -1
  358. package/lib/opCompressor.d.ts.map +0 -1
  359. package/lib/opCompressor.js +0 -46
  360. package/lib/opCompressor.js.map +0 -1
  361. package/lib/opDecompressor.d.ts.map +0 -1
  362. package/lib/opDecompressor.js.map +0 -1
  363. package/src/batchManager.ts +0 -108
  364. package/src/opCompressor.ts +0 -59
  365. package/src/opDecompressor.ts +0 -82
@@ -3,1004 +3,1057 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
7
6
  import {
8
- FluidObject,
9
- IRequest,
10
- IResponse,
11
- IFluidHandle,
12
- } from "@fluidframework/core-interfaces";
7
+ IDisposable,
8
+ ITelemetryLogger,
9
+ ITelemetryProperties,
10
+ } from "@fluidframework/common-definitions";
11
+ import { FluidObject, IRequest, IResponse, IFluidHandle } from "@fluidframework/core-interfaces";
13
12
  import {
14
- IAudience,
15
- IDeltaManager,
16
- AttachState,
17
- ILoaderOptions,
13
+ IAudience,
14
+ IDeltaManager,
15
+ AttachState,
16
+ ILoaderOptions,
18
17
  } from "@fluidframework/container-definitions";
19
- import {
20
- assert,
21
- Deferred,
22
- LazyPromise,
23
- TypedEventEmitter,
24
- } from "@fluidframework/common-utils";
18
+ import { assert, Deferred, LazyPromise, TypedEventEmitter } from "@fluidframework/common-utils";
25
19
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
26
20
  import { readAndParse } from "@fluidframework/driver-utils";
27
21
  import { BlobTreeEntry } from "@fluidframework/protocol-base";
28
22
  import {
29
- IClientDetails,
30
- IDocumentMessage,
31
- IQuorumClients,
32
- ISequencedDocumentMessage,
33
- ISnapshotTree,
34
- ITreeEntry,
23
+ IClientDetails,
24
+ IDocumentMessage,
25
+ IQuorumClients,
26
+ ISequencedDocumentMessage,
27
+ ISnapshotTree,
28
+ ITreeEntry,
35
29
  } from "@fluidframework/protocol-definitions";
30
+ import { IContainerRuntime } from "@fluidframework/container-runtime-definitions";
36
31
  import {
37
- IContainerRuntime,
38
- } from "@fluidframework/container-runtime-definitions";
39
- import {
40
- BindState,
41
- channelsTreeName,
42
- CreateChildSummarizerNodeFn,
43
- CreateChildSummarizerNodeParam,
44
- FluidDataStoreRegistryEntry,
45
- IAttachMessage,
46
- IFluidDataStoreChannel,
47
- IFluidDataStoreContext,
48
- IFluidDataStoreContextDetached,
49
- IFluidDataStoreContextEvents,
50
- IFluidDataStoreRegistry,
51
- IGarbageCollectionData,
52
- IGarbageCollectionDetailsBase,
53
- IInboundSignalMessage,
54
- IProvideFluidDataStoreFactory,
55
- ISummarizeInternalResult,
56
- ISummarizeResult,
57
- ISummarizerNodeWithGC,
58
- SummarizeInternalFn,
59
- ITelemetryContext,
32
+ BindState,
33
+ channelsTreeName,
34
+ CreateChildSummarizerNodeFn,
35
+ CreateChildSummarizerNodeParam,
36
+ FluidDataStoreRegistryEntry,
37
+ IAttachMessage,
38
+ IFluidDataStoreChannel,
39
+ IFluidDataStoreContext,
40
+ IFluidDataStoreContextDetached,
41
+ IFluidDataStoreContextEvents,
42
+ IFluidDataStoreRegistry,
43
+ IGarbageCollectionData,
44
+ IGarbageCollectionDetailsBase,
45
+ IInboundSignalMessage,
46
+ IProvideFluidDataStoreFactory,
47
+ ISummarizeInternalResult,
48
+ ISummarizeResult,
49
+ ISummarizerNodeWithGC,
50
+ SummarizeInternalFn,
51
+ ITelemetryContext,
60
52
  } from "@fluidframework/runtime-definitions";
53
+ import { addBlobToSummary, convertSummaryTreeToITree } from "@fluidframework/runtime-utils";
61
54
  import {
62
- addBlobToSummary,
63
- convertSummaryTreeToITree,
64
- packagePathToTelemetryProperty,
65
- } from "@fluidframework/runtime-utils";
66
- import {
67
- ChildLogger,
68
- loggerToMonitoringContext,
69
- LoggingError,
70
- MonitoringContext,
71
- TelemetryDataTag,
72
- ThresholdCounter,
55
+ ChildLogger,
56
+ loggerToMonitoringContext,
57
+ LoggingError,
58
+ MonitoringContext,
59
+ TelemetryDataTag,
60
+ ThresholdCounter,
73
61
  } from "@fluidframework/telemetry-utils";
74
62
  import {
75
- DataCorruptionError,
76
- DataProcessingError,
77
- extractSafePropertiesFromMessage,
63
+ DataCorruptionError,
64
+ DataProcessingError,
65
+ extractSafePropertiesFromMessage,
78
66
  } from "@fluidframework/container-utils";
79
67
 
80
68
  import { ContainerRuntime } from "./containerRuntime";
81
69
  import {
82
- dataStoreAttributesBlobName,
83
- hasIsolatedChannels,
84
- wrapSummaryInChannelsTree,
85
- ReadFluidDataStoreAttributes,
86
- WriteFluidDataStoreAttributes,
87
- getAttributesFormatVersion,
88
- getFluidDataStoreAttributes,
70
+ dataStoreAttributesBlobName,
71
+ hasIsolatedChannels,
72
+ wrapSummaryInChannelsTree,
73
+ ReadFluidDataStoreAttributes,
74
+ WriteFluidDataStoreAttributes,
75
+ getAttributesFormatVersion,
76
+ getFluidDataStoreAttributes,
89
77
  } from "./summaryFormat";
90
- import { throwOnTombstoneUsageKey } from "./garbageCollection";
78
+ import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
79
+ import { sendGCUnexpectedUsageEvent } from "./garbageCollectionHelpers";
91
80
  import { summarizerClientType } from "./summarizerClientElection";
92
81
 
93
82
  function createAttributes(
94
- pkg: readonly string[],
95
- isRootDataStore: boolean,
83
+ pkg: readonly string[],
84
+ isRootDataStore: boolean,
96
85
  ): WriteFluidDataStoreAttributes {
97
- const stringifiedPkg = JSON.stringify(pkg);
98
- return {
99
- pkg: stringifiedPkg,
100
- summaryFormatVersion: 2,
101
- isRootDataStore,
102
- };
86
+ const stringifiedPkg = JSON.stringify(pkg);
87
+ return {
88
+ pkg: stringifiedPkg,
89
+ summaryFormatVersion: 2,
90
+ isRootDataStore,
91
+ };
103
92
  }
104
- export function createAttributesBlob(
105
- pkg: readonly string[],
106
- isRootDataStore: boolean,
107
- ): ITreeEntry {
108
- const attributes = createAttributes(pkg, isRootDataStore);
109
- return new BlobTreeEntry(dataStoreAttributesBlobName, JSON.stringify(attributes));
93
+ export function createAttributesBlob(pkg: readonly string[], isRootDataStore: boolean): ITreeEntry {
94
+ const attributes = createAttributes(pkg, isRootDataStore);
95
+ return new BlobTreeEntry(dataStoreAttributesBlobName, JSON.stringify(attributes));
110
96
  }
111
97
 
112
98
  interface ISnapshotDetails {
113
- pkg: readonly string[];
114
- isRootDataStore: boolean;
115
- snapshot?: ISnapshotTree;
99
+ pkg: readonly string[];
100
+ isRootDataStore: boolean;
101
+ snapshot?: ISnapshotTree;
116
102
  }
117
103
 
118
104
  interface FluidDataStoreMessage {
119
- content: any;
120
- type: string;
105
+ content: any;
106
+ type: string;
121
107
  }
122
108
 
123
109
  /** Properties necessary for creating a FluidDataStoreContext */
124
110
  export interface IFluidDataStoreContextProps {
125
- readonly id: string;
126
- readonly runtime: ContainerRuntime;
127
- readonly storage: IDocumentStorageService;
128
- readonly scope: FluidObject;
129
- readonly createSummarizerNodeFn: CreateChildSummarizerNodeFn;
130
- readonly pkg?: Readonly<string[]>;
111
+ readonly id: string;
112
+ readonly runtime: ContainerRuntime;
113
+ readonly storage: IDocumentStorageService;
114
+ readonly scope: FluidObject;
115
+ readonly createSummarizerNodeFn: CreateChildSummarizerNodeFn;
116
+ readonly pkg?: Readonly<string[]>;
131
117
  }
132
118
 
133
119
  /** Properties necessary for creating a local FluidDataStoreContext */
134
120
  export interface ILocalFluidDataStoreContextProps extends IFluidDataStoreContextProps {
135
- readonly pkg: Readonly<string[]> | undefined;
136
- readonly snapshotTree: ISnapshotTree | undefined;
137
- readonly isRootDataStore: boolean | undefined;
138
- readonly makeLocallyVisibleFn: () => void;
139
- /**
140
- * @deprecated 0.16 Issue #1635, #3631
141
- */
142
- readonly createProps?: any;
121
+ readonly pkg: Readonly<string[]> | undefined;
122
+ readonly snapshotTree: ISnapshotTree | undefined;
123
+ readonly isRootDataStore: boolean | undefined;
124
+ readonly makeLocallyVisibleFn: () => void;
125
+ /**
126
+ * @deprecated 0.16 Issue #1635, #3631
127
+ */
128
+ readonly createProps?: any;
143
129
  }
144
130
 
145
131
  /** Properties necessary for creating a remote FluidDataStoreContext */
146
132
  export interface IRemoteFluidDataStoreContextProps extends IFluidDataStoreContextProps {
147
- readonly snapshotTree: ISnapshotTree | undefined;
148
- readonly getBaseGCDetails: () => Promise<IGarbageCollectionDetailsBase | undefined>;
133
+ readonly snapshotTree: ISnapshotTree | undefined;
134
+ readonly getBaseGCDetails: () => Promise<IGarbageCollectionDetailsBase | undefined>;
149
135
  }
150
136
 
151
137
  /**
152
138
  * Represents the context for the store. This context is passed to the store runtime.
153
139
  */
154
- export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidDataStoreContextEvents> implements
155
- IFluidDataStoreContext,
156
- IDisposable {
157
- public get packagePath(): readonly string[] {
158
- assert(this.pkg !== undefined, 0x139 /* "Undefined package path" */);
159
- return this.pkg;
160
- }
161
-
162
- public get options(): ILoaderOptions {
163
- return this._containerRuntime.options;
164
- }
165
-
166
- public get clientId(): string | undefined {
167
- return this._containerRuntime.clientId;
168
- }
169
-
170
- public get clientDetails(): IClientDetails {
171
- return this._containerRuntime.clientDetails;
172
- }
173
-
174
- public get logger(): ITelemetryLogger {
175
- return this._containerRuntime.logger;
176
- }
177
-
178
- public get deltaManager(): IDeltaManager<ISequencedDocumentMessage, IDocumentMessage> {
179
- return this._containerRuntime.deltaManager;
180
- }
181
-
182
- public get connected(): boolean {
183
- return this._containerRuntime.connected;
184
- }
185
-
186
- public get IFluidHandleContext() {
187
- return this._containerRuntime.IFluidHandleContext;
188
- }
189
-
190
- public get containerRuntime(): IContainerRuntime {
191
- return this._containerRuntime;
192
- }
193
-
194
- public get isLoaded(): boolean {
195
- return this.loaded;
196
- }
197
-
198
- public get baseSnapshot(): ISnapshotTree | undefined {
199
- return this._baseSnapshot;
200
- }
201
-
202
- private _disposed = false;
203
- public get disposed() { return this._disposed; }
204
-
205
- /**
206
- * Tombstone is a temporary feature that prevents a data store from sending / receiving ops, signals and from
207
- * loading.
208
- */
209
- private _tombstoned = false;
210
- public get tombstoned() { return this._tombstoned; }
211
- /** If true, throw an error when a tombstone data store is used. */
212
- private readonly throwOnTombstoneUsage: boolean;
213
-
214
- public get attachState(): AttachState {
215
- return this._attachState;
216
- }
217
-
218
- public get IFluidDataStoreRegistry(): IFluidDataStoreRegistry | undefined {
219
- return this.registry;
220
- }
221
-
222
- /**
223
- * A datastore is considered as root if it
224
- * 1. is root in memory - see isInMemoryRoot
225
- * 2. is root as part of the base snapshot that the datastore loaded from
226
- * @returns whether a datastore is root
227
- */
228
- public async isRoot(): Promise<boolean> {
229
- return this.isInMemoryRoot() || (await this.getInitialSnapshotDetails()).isRootDataStore;
230
- }
231
-
232
- /**
233
- * There are 3 states where isInMemoryRoot needs to be true
234
- * 1. when a datastore becomes aliased. This can happen for both remote and local datastores
235
- * 2. when a datastore is created locally as root
236
- * 3. when a datastore is created locally as root and is rehydrated
237
- * @returns whether a datastore is root in memory
238
- */
239
- protected isInMemoryRoot(): boolean {
240
- return this._isInMemoryRoot;
241
- }
242
-
243
- protected registry: IFluidDataStoreRegistry | undefined;
244
-
245
- protected detachedRuntimeCreation = false;
246
- /** @deprecated - To be replaced by calling makeLocallyVisible directly */
247
- public readonly bindToContext: () => void;
248
- protected channel: IFluidDataStoreChannel | undefined;
249
- private loaded = false;
250
- protected pending: ISequencedDocumentMessage[] | undefined = [];
251
- protected channelDeferred: Deferred<IFluidDataStoreChannel> | undefined;
252
- private _baseSnapshot: ISnapshotTree | undefined;
253
- protected _attachState: AttachState;
254
- private _isInMemoryRoot: boolean = false;
255
- protected readonly summarizerNode: ISummarizerNodeWithGC;
256
- private readonly mc: MonitoringContext;
257
- private readonly thresholdOpsCounter: ThresholdCounter;
258
- private static readonly pendingOpsCountThreshold = 1000;
259
-
260
- // The used routes of this node as per the last GC run. This is used to update the used routes of the channel
261
- // if it realizes after GC is run.
262
- private lastUsedRoutes: string[] | undefined;
263
-
264
- public readonly id: string;
265
- private readonly _containerRuntime: ContainerRuntime;
266
- public readonly storage: IDocumentStorageService;
267
- public readonly scope: FluidObject;
268
- protected pkg?: readonly string[];
269
-
270
- constructor(
271
- props: IFluidDataStoreContextProps,
272
- private readonly existing: boolean,
273
- private bindState: BindState, // Used to assert for state tracking purposes
274
- public readonly isLocalDataStore: boolean,
275
- private readonly makeLocallyVisibleFn: () => void,
276
- ) {
277
- super();
278
-
279
- this._containerRuntime = props.runtime;
280
- this.id = props.id;
281
- this.storage = props.storage;
282
- this.scope = props.scope;
283
- this.pkg = props.pkg;
284
-
285
- // URIs use slashes as delimiters. Handles use URIs.
286
- // Thus having slashes in types almost guarantees trouble down the road!
287
- assert(!this.id.includes("/"), 0x13a /* Data store ID contains slash */);
288
-
289
- this._attachState = this.containerRuntime.attachState !== AttachState.Detached && this.existing ?
290
- this.containerRuntime.attachState : AttachState.Detached;
291
-
292
- this.bindToContext = () => {
293
- assert(this.bindState === BindState.NotBound, 0x13b /* "datastore context is already in bound state" */);
294
- this.bindState = BindState.Binding;
295
- assert(this.channel !== undefined, 0x13c /* "undefined channel on datastore context" */);
296
- this.makeLocallyVisible();
297
- this.bindState = BindState.Bound;
298
- };
299
-
300
- const thisSummarizeInternal =
301
- async (fullTree: boolean, trackState: boolean, telemetryContext?: ITelemetryContext) =>
302
- this.summarizeInternal(fullTree, trackState, telemetryContext);
303
-
304
- this.summarizerNode = props.createSummarizerNodeFn(
305
- thisSummarizeInternal,
306
- async (fullGC?: boolean) => this.getGCDataInternal(fullGC),
307
- async () => this.getBaseGCDetails(),
308
- );
309
-
310
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "FluidDataStoreContext"));
311
- this.thresholdOpsCounter = new ThresholdCounter(FluidDataStoreContext.pendingOpsCountThreshold, this.mc.logger);
312
-
313
- // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
314
- this.throwOnTombstoneUsage =
315
- this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
316
- this.clientDetails.type !== summarizerClientType;
317
- }
318
-
319
- public dispose(): void {
320
- if (this._disposed) {
321
- return;
322
- }
323
- this._disposed = true;
324
-
325
- // Dispose any pending runtime after it gets fulfilled
326
- // Errors are logged where this.channelDeferred is consumed/generated (realizeCore(), bindRuntime())
327
- if (this.channelDeferred) {
328
- this.channelDeferred.promise.then((runtime) => {
329
- runtime.dispose();
330
- }).catch((error) => {});
331
- }
332
- }
333
-
334
- public setTombstone(tombstone: boolean) {
335
- if (this.tombstoned === tombstone) {
336
- return;
337
- }
338
-
339
- this._tombstoned = tombstone;
340
- }
341
-
342
- private rejectDeferredRealize(reason: string, packageName?: string): never {
343
- throw new LoggingError(reason, { packageName: { value: packageName, tag: TelemetryDataTag.CodeArtifact } });
344
- }
345
-
346
- public async realize(): Promise<IFluidDataStoreChannel> {
347
- assert(!this.detachedRuntimeCreation, 0x13d /* "Detached runtime creation on realize()" */);
348
- if (!this.channelDeferred) {
349
- this.channelDeferred = new Deferred<IFluidDataStoreChannel>();
350
- this.realizeCore(this.existing).catch((error) => {
351
- const errorWrapped = DataProcessingError.wrapIfUnrecognized(error, "realizeFluidDataStoreContext");
352
- errorWrapped.addTelemetryProperties({
353
- fluidDataStoreId: {
354
- value: this.id,
355
- tag: TelemetryDataTag.CodeArtifact,
356
- },
357
- });
358
- this.channelDeferred?.reject(errorWrapped);
359
- this.logger.sendErrorEvent({ eventName: "RealizeError" }, errorWrapped);
360
- });
361
- }
362
- return this.channelDeferred.promise;
363
- }
364
-
365
- protected async factoryFromPackagePath(packages?: readonly string[]) {
366
- assert(this.pkg === packages, 0x13e /* "Unexpected package path" */);
367
- if (packages === undefined) {
368
- this.rejectDeferredRealize("packages is undefined");
369
- }
370
-
371
- let entry: FluidDataStoreRegistryEntry | undefined;
372
- let registry: IFluidDataStoreRegistry | undefined = this._containerRuntime.IFluidDataStoreRegistry;
373
- let lastPkg: string | undefined;
374
- for (const pkg of packages) {
375
- if (!registry) {
376
- this.rejectDeferredRealize("No registry for package", lastPkg);
377
- }
378
- lastPkg = pkg;
379
- entry = await registry.get(pkg);
380
- if (!entry) {
381
- this.rejectDeferredRealize("Registry does not contain entry for the package", pkg);
382
- }
383
- registry = entry.IFluidDataStoreRegistry;
384
- }
385
- const factory = entry?.IFluidDataStoreFactory;
386
- if (factory === undefined) {
387
- this.rejectDeferredRealize("Can't find factory for package", lastPkg);
388
- }
389
-
390
- return { factory, registry };
391
- }
392
-
393
- private async realizeCore(existing: boolean): Promise<void> {
394
- const details = await this.getInitialSnapshotDetails();
395
- // Base snapshot is the baseline where pending ops are applied to.
396
- // It is important that this be in sync with the pending ops, and also
397
- // that it is set here, before bindRuntime is called.
398
- this._baseSnapshot = details.snapshot;
399
- const packages = details.pkg;
400
-
401
- const { factory, registry } = await this.factoryFromPackagePath(packages);
402
-
403
- assert(this.registry === undefined, 0x13f /* "datastore context registry is already set" */);
404
- this.registry = registry;
405
-
406
- const channel = await factory.instantiateDataStore(this, existing);
407
- assert(channel !== undefined, 0x140 /* "undefined channel on datastore context" */);
408
- this.bindRuntime(channel);
409
- }
410
-
411
- /**
412
- * Notifies this object about changes in the connection state.
413
- * @param value - New connection state.
414
- * @param clientId - ID of the client. Its old ID when in disconnected state and
415
- * its new client ID when we are connecting or connected.
416
- */
417
- public setConnectionState(connected: boolean, clientId?: string) {
418
- // ConnectionState should not fail in tombstone mode as this is internally run
419
- this.verifyNotClosed("setConnectionState", false /* checkTombstone */);
420
-
421
- // Connection events are ignored if the store is not yet loaded
422
- if (!this.loaded) {
423
- return;
424
- }
425
-
426
- assert(this.connected === connected, 0x141 /* "Unexpected connected state" */);
427
-
428
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
429
- this.channel!.setConnectionState(connected, clientId);
430
- }
431
-
432
- public process(messageArg: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void {
433
- this.verifyNotClosed("process", true, extractSafePropertiesFromMessage(messageArg));
434
-
435
- const innerContents = messageArg.contents as FluidDataStoreMessage;
436
- const message = {
437
- ...messageArg,
438
- type: innerContents.type,
439
- contents: innerContents.content,
440
- };
441
-
442
- this.summarizerNode.recordChange(message);
443
-
444
- if (this.loaded) {
445
- return this.channel?.process(message, local, localOpMetadata);
446
- } else {
447
- assert(!local, 0x142 /* "local store channel is not loaded" */);
448
- assert(this.pending !== undefined, 0x23d /* "pending is undefined" */);
449
- this.pending.push(message);
450
- this.thresholdOpsCounter.sendIfMultiple("StorePendingOps", this.pending.length);
451
- }
452
- }
453
-
454
- public processSignal(message: IInboundSignalMessage, local: boolean): void {
455
- this.verifyNotClosed("processSignal");
456
-
457
- // Signals are ignored if the store is not yet loaded
458
- if (!this.loaded) {
459
- return;
460
- }
461
-
462
- this.channel?.processSignal(message, local);
463
- }
464
-
465
- public getQuorum(): IQuorumClients {
466
- return this._containerRuntime.getQuorum();
467
- }
468
-
469
- public getAudience(): IAudience {
470
- return this._containerRuntime.getAudience();
471
- }
472
-
473
- /**
474
- * Returns a summary at the current sequence number.
475
- * @param fullTree - true to bypass optimizations and force a full summary tree
476
- * @param trackState - This tells whether we should track state from this summary.
477
- * @param telemetryContext - summary data passed through the layers for telemetry purposes
478
- */
479
- public async summarize(
480
- fullTree: boolean = false,
481
- trackState: boolean = true,
482
- telemetryContext?: ITelemetryContext,
483
- ): Promise<ISummarizeResult> {
484
- return this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
485
- }
486
-
487
- private async summarizeInternal(
488
- fullTree: boolean,
489
- trackState: boolean,
490
- telemetryContext?: ITelemetryContext,
491
- ): Promise<ISummarizeInternalResult> {
492
- await this.realize();
493
-
494
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
495
- const summarizeResult = await this.channel!.summarize(fullTree, trackState, telemetryContext);
496
-
497
- // Wrap dds summaries in .channels subtree.
498
- wrapSummaryInChannelsTree(summarizeResult);
499
- const pathPartsForChildren = [channelsTreeName];
500
-
501
- // Add data store's attributes to the summary.
502
- const { pkg } = await this.getInitialSnapshotDetails();
503
- const isRoot = await this.isRoot();
504
- const attributes = createAttributes(pkg, isRoot);
505
- addBlobToSummary(summarizeResult, dataStoreAttributesBlobName, JSON.stringify(attributes));
506
-
507
- // If we are not referenced, mark the summary tree as unreferenced. Also, update unreferenced blob
508
- // size in the summary stats with the blobs size of this data store.
509
- if (!this.summarizerNode.isReferenced()) {
510
- summarizeResult.summary.unreferenced = true;
511
- summarizeResult.stats.unreferencedBlobSize = summarizeResult.stats.totalBlobSize;
512
- }
513
-
514
- return {
515
- ...summarizeResult,
516
- id: this.id,
517
- pathPartsForChildren,
518
- };
519
- }
520
-
521
- /**
522
- * Returns the data used for garbage collection. This includes a list of GC nodes that represent this data store
523
- * including any of its child channel contexts. Each node has a set of outbound routes to other GC nodes in the
524
- * document.
525
- * If there is no new data in this data store since the last summary, previous GC data is used.
526
- * If there is new data, the GC data is generated again (by calling getGCDataInternal).
527
- * @param fullGC - true to bypass optimizations and force full generation of GC data.
528
- */
529
- public async getGCData(fullGC: boolean = false): Promise<IGarbageCollectionData> {
530
- return this.summarizerNode.getGCData(fullGC);
531
- }
532
-
533
- /**
534
- * Generates data used for garbage collection. This is called when there is new data since last summary. It
535
- * realizes the data store and calls into each channel context to get its GC data.
536
- * @param fullGC - true to bypass optimizations and force full generation of GC data.
537
- */
538
- private async getGCDataInternal(fullGC: boolean = false): Promise<IGarbageCollectionData> {
539
- await this.realize();
540
- assert(this.channel !== undefined, 0x143 /* "Channel should not be undefined when running GC" */);
541
-
542
- return this.channel.getGCData(fullGC);
543
- }
544
-
545
- /**
546
- * After GC has run, called to notify the data store of routes used in it. These are used for the following:
547
- *
548
- * 1. To identify if this data store is being referenced in the document or not.
549
- *
550
- * 2. To determine if it needs to re-summarize in case used routes changed since last summary.
551
- *
552
- * 3. These are added to the summary generated by the data store.
553
- *
554
- * 4. To notify child contexts of their used routes. This is done immediately if the data store is loaded.
555
- * Else, it is done when realizing the data store.
556
- *
557
- * 5. To update the timestamp when this data store or any children are marked as unreferenced.
558
- *
559
- * @param usedRoutes - The routes that are used in this data store.
560
- */
561
- public updateUsedRoutes(usedRoutes: string[]) {
562
- // Update the used routes in this data store's summarizer node.
563
- this.summarizerNode.updateUsedRoutes(usedRoutes);
564
-
565
- /**
566
- * Store the used routes to update the channel if the data store is not loaded yet. If the used routes changed
567
- * since the previous run, the data store will be loaded during summarize since the used state changed. So, it's
568
- * safe to only store the last used routes.
569
- */
570
- this.lastUsedRoutes = usedRoutes;
571
-
572
- // If we are loaded, call the channel so it can update the used routes of the child contexts.
573
- // If we are not loaded, we will update this when we are realized.
574
- if (this.loaded) {
575
- this.updateChannelUsedRoutes();
576
- }
577
- }
578
-
579
- /**
580
- * Called when a new outbound reference is added to another node. This is used by garbage collection to identify
581
- * all references added in the system.
582
- * @param srcHandle - The handle of the node that added the reference.
583
- * @param outboundHandle - The handle of the outbound node that is referenced.
584
- */
585
- public addedGCOutboundReference(srcHandle: IFluidHandle, outboundHandle: IFluidHandle) {
586
- this._containerRuntime.addedGCOutboundReference(srcHandle, outboundHandle);
587
- }
588
-
589
- /**
590
- * Updates the used routes of the channel and its child contexts. The channel must be loaded before calling this.
591
- * It is called in these two scenarios:
592
- * 1. When the used routes of the data store is updated and the data store is loaded.
593
- * 2. When the data store is realized. This updates the channel's used routes as per last GC run.
594
- */
595
- private updateChannelUsedRoutes() {
596
- assert(this.loaded, 0x144 /* "Channel should be loaded when updating used routes" */);
597
- assert(this.channel !== undefined, 0x145 /* "Channel should be present when data store is loaded" */);
598
-
599
- // If there is no lastUsedRoutes, GC has not run up until this point.
600
- if (this.lastUsedRoutes === undefined) {
601
- return;
602
- }
603
-
604
- // Remove the route to this data store, if it exists.
605
- const usedChannelRoutes = this.lastUsedRoutes.filter(
606
- (id: string) => { return id !== "/" && id !== ""; },
607
- );
608
- this.channel.updateUsedRoutes(usedChannelRoutes);
609
- }
610
-
611
- /**
612
- * @deprecated 0.18.Should call request on the runtime directly
613
- */
614
- public async request(request: IRequest): Promise<IResponse> {
615
- const runtime = await this.realize();
616
- return runtime.request(request);
617
- }
618
-
619
- public submitMessage(type: string, content: any, localOpMetadata: unknown): void {
620
- this.verifyNotClosed("submitMessage");
621
- assert(!!this.channel, 0x146 /* "Channel must exist when submitting message" */);
622
- const fluidDataStoreContent: FluidDataStoreMessage = {
623
- content,
624
- type,
625
- };
626
- this._containerRuntime.submitDataStoreOp(
627
- this.id,
628
- fluidDataStoreContent,
629
- localOpMetadata);
630
- }
631
-
632
- /**
633
- * This is called from a SharedSummaryBlock that does not generate ops but only wants to be part of the summary.
634
- * It indicates that there is data in the object that needs to be summarized.
635
- * We will update the latestSequenceNumber of the summary tracker of this
636
- * store and of the object's channel.
637
- *
638
- * @param address - The address of the channel that is dirty.
639
- *
640
- */
641
- public setChannelDirty(address: string): void {
642
- this.verifyNotClosed("setChannelDirty");
643
-
644
- // Get the latest sequence number.
645
- const latestSequenceNumber = this.deltaManager.lastSequenceNumber;
646
-
647
- this.summarizerNode.invalidate(latestSequenceNumber);
648
-
649
- const channelSummarizerNode = this.summarizerNode.getChild(address);
650
-
651
- if (channelSummarizerNode) {
652
- channelSummarizerNode.invalidate(latestSequenceNumber); // TODO: lazy load problem?
653
- }
654
- }
655
-
656
- public submitSignal(type: string, content: any) {
657
- this.verifyNotClosed("submitSignal");
658
-
659
- assert(!!this.channel, 0x147 /* "Channel must exist on submitting signal" */);
660
- return this._containerRuntime.submitDataStoreSignal(this.id, type, content);
661
- }
662
-
663
- /**
664
- * This is called by the data store channel when it becomes locally visible indicating that it is ready to become
665
- * globally visible now.
666
- */
667
- public makeLocallyVisible() {
668
- assert(this.channel !== undefined, 0x2cf /* "undefined channel on datastore context" */);
669
- this.makeLocallyVisibleFn();
670
- }
671
-
672
- protected bindRuntime(channel: IFluidDataStoreChannel) {
673
- if (this.channel) {
674
- throw new Error("Runtime already bound");
675
- }
676
-
677
- try {
678
- assert(!this.detachedRuntimeCreation, 0x148 /* "Detached runtime creation on runtime bind" */);
679
- assert(this.channelDeferred !== undefined, 0x149 /* "Undefined channel deferral" */);
680
- assert(this.pkg !== undefined, 0x14a /* "Undefined package path" */);
681
-
682
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
683
- const pending = this.pending!;
684
-
685
- // Apply all pending ops
686
- for (const op of pending) {
687
- channel.process(op, false, undefined /* localOpMetadata */);
688
- }
689
-
690
- this.thresholdOpsCounter.send("ProcessPendingOps", pending.length);
691
- this.pending = undefined;
692
-
693
- // And now mark the runtime active
694
- this.loaded = true;
695
- this.channel = channel;
696
-
697
- // Freeze the package path to ensure that someone doesn't modify it when it is
698
- // returned in packagePath().
699
- Object.freeze(this.pkg);
700
-
701
- /**
702
- * Update the used routes of the channel. If GC has run before this data store was realized, we will have
703
- * the used routes saved. So, this will ensure that all the child contexts have up-to-date used routes as
704
- * per the last time GC was run.
705
- * Also, this data store may have been realized during summarize. In that case, the child contexts need to
706
- * have their used routes updated to determine if its needs to summarize again and to add it to the summary.
707
- */
708
- this.updateChannelUsedRoutes();
709
-
710
- // And notify the pending promise it is now available
711
- this.channelDeferred.resolve(this.channel);
712
- } catch (error) {
713
- this.channelDeferred?.reject(error);
714
- this.logger.sendErrorEvent(
715
- { eventName: "BindRuntimeError", fluidDataStoreId: {
716
- value: this.id,
717
- tag: TelemetryDataTag.CodeArtifact,
718
- } },
719
- error);
720
- }
721
- }
722
-
723
- public async getAbsoluteUrl(relativeUrl: string): Promise<string | undefined> {
724
- if (this.attachState !== AttachState.Attached) {
725
- return undefined;
726
- }
727
- return this._containerRuntime.getAbsoluteUrl(relativeUrl);
728
- }
729
-
730
- public abstract generateAttachMessage(): IAttachMessage;
731
-
732
- public abstract getInitialSnapshotDetails(): Promise<ISnapshotDetails>;
733
-
734
- /**
735
- * @deprecated Sets the datastore as root, for aliasing purposes: #7948
736
- * This method should not be used outside of the aliasing context.
737
- * It will be removed, as the source of truth for this flag will be the aliasing blob.
738
- */
739
- public setInMemoryRoot(): void {
740
- this._isInMemoryRoot = true;
741
- }
742
-
743
- public abstract getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
744
-
745
- public reSubmit(contents: any, localOpMetadata: unknown) {
746
- assert(!!this.channel, 0x14b /* "Channel must exist when resubmitting ops" */);
747
- const innerContents = contents as FluidDataStoreMessage;
748
- this.channel.reSubmit(innerContents.type, innerContents.content, localOpMetadata);
749
- }
750
-
751
- public rollback(contents: any, localOpMetadata: unknown) {
752
- if (!this.channel) {
753
- throw new Error("Channel must exist when rolling back ops");
754
- }
755
- if (!this.channel.rollback) {
756
- throw new Error("Channel doesn't support rollback");
757
- }
758
- const innerContents = contents as FluidDataStoreMessage;
759
- this.channel.rollback(innerContents.type, innerContents.content, localOpMetadata);
760
- }
761
-
762
- public async applyStashedOp(contents: any): Promise<unknown> {
763
- if (!this.channel) {
764
- await this.realize();
765
- }
766
- assert(!!this.channel, 0x14c /* "Channel must exist when rebasing ops" */);
767
- const innerContents = contents as FluidDataStoreMessage;
768
- return this.channel.applyStashedOp(innerContents.content);
769
- }
770
-
771
- private verifyNotClosed(callSite: string, checkTombstone = true, safeTelemetryProps: ITelemetryProperties = {}) {
772
- if (this._disposed) {
773
- throw new Error(`Context is closed! Call site [${callSite}]`);
774
- }
775
-
776
- if (checkTombstone && this.tombstoned) {
777
- const messageString = `Context is tombstoned! Call site [${callSite}]`;
778
- const error = new DataCorruptionError(messageString, {
779
- errorMessage: messageString,
780
- ...safeTelemetryProps,
781
- });
782
-
783
- // Always log an error when tombstoned data store is used. However, throw an error only if
784
- // throwOnTombstoneUsage is set.
785
- this.mc.logger.sendErrorEvent({
786
- eventName: "GC_Tombstone_DataStore_Changed",
787
- callSite,
788
- pkg: packagePathToTelemetryProperty(this.pkg),
789
- }, error);
790
- // Always log an error when tombstoned data store is used. However, throw an error only if
791
- // throwOnTombstoneUsage is set and the client is not a summarizer.
792
- if (this.throwOnTombstoneUsage) {
793
- throw error;
794
- }
795
- }
796
- }
797
-
798
- public getCreateChildSummarizerNodeFn(id: string, createParam: CreateChildSummarizerNodeParam) {
799
- return (
800
- summarizeInternal: SummarizeInternalFn,
801
- getGCDataFn: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
802
- getBaseGCDetailsFn: () => Promise<IGarbageCollectionDetailsBase>,
803
- ) => this.summarizerNode.createChild(
804
- summarizeInternal,
805
- id,
806
- createParam,
807
- // DDS will not create failure summaries
808
- { throwOnFailure: true },
809
- getGCDataFn,
810
- getBaseGCDetailsFn,
811
- );
812
- }
813
-
814
- public async uploadBlob(blob: ArrayBufferLike): Promise<IFluidHandle<ArrayBufferLike>> {
815
- return this.containerRuntime.uploadBlob(blob);
816
- }
140
+ export abstract class FluidDataStoreContext
141
+ extends TypedEventEmitter<IFluidDataStoreContextEvents>
142
+ implements IFluidDataStoreContext, IDisposable
143
+ {
144
+ public get packagePath(): readonly string[] {
145
+ assert(this.pkg !== undefined, 0x139 /* "Undefined package path" */);
146
+ return this.pkg;
147
+ }
148
+
149
+ public get options(): ILoaderOptions {
150
+ return this._containerRuntime.options;
151
+ }
152
+
153
+ public get clientId(): string | undefined {
154
+ return this._containerRuntime.clientId;
155
+ }
156
+
157
+ public get clientDetails(): IClientDetails {
158
+ return this._containerRuntime.clientDetails;
159
+ }
160
+
161
+ public get logger(): ITelemetryLogger {
162
+ return this._containerRuntime.logger;
163
+ }
164
+
165
+ public get deltaManager(): IDeltaManager<ISequencedDocumentMessage, IDocumentMessage> {
166
+ return this._containerRuntime.deltaManager;
167
+ }
168
+
169
+ public get connected(): boolean {
170
+ return this._containerRuntime.connected;
171
+ }
172
+
173
+ public get IFluidHandleContext() {
174
+ return this._containerRuntime.IFluidHandleContext;
175
+ }
176
+
177
+ public get containerRuntime(): IContainerRuntime {
178
+ return this._containerRuntime;
179
+ }
180
+
181
+ public ensureNoDataModelChanges<T>(callback: () => T): T {
182
+ return this._containerRuntime.ensureNoDataModelChanges(callback);
183
+ }
184
+
185
+ public get isLoaded(): boolean {
186
+ return this.loaded;
187
+ }
188
+
189
+ public get baseSnapshot(): ISnapshotTree | undefined {
190
+ return this._baseSnapshot;
191
+ }
192
+
193
+ private _disposed = false;
194
+ public get disposed() {
195
+ return this._disposed;
196
+ }
197
+
198
+ /**
199
+ * Tombstone is a temporary feature that prevents a data store from sending / receiving ops, signals and from
200
+ * loading.
201
+ */
202
+ private _tombstoned = false;
203
+ public get tombstoned() {
204
+ return this._tombstoned;
205
+ }
206
+ /** If true, throw an error when a tombstone data store is used. */
207
+ private readonly throwOnTombstoneUsage: boolean;
208
+
209
+ public get attachState(): AttachState {
210
+ return this._attachState;
211
+ }
212
+
213
+ public get IFluidDataStoreRegistry(): IFluidDataStoreRegistry | undefined {
214
+ return this.registry;
215
+ }
216
+
217
+ /**
218
+ * A datastore is considered as root if it
219
+ * 1. is root in memory - see isInMemoryRoot
220
+ * 2. is root as part of the base snapshot that the datastore loaded from
221
+ * @returns whether a datastore is root
222
+ */
223
+ public async isRoot(): Promise<boolean> {
224
+ return this.isInMemoryRoot() || (await this.getInitialSnapshotDetails()).isRootDataStore;
225
+ }
226
+
227
+ /**
228
+ * There are 3 states where isInMemoryRoot needs to be true
229
+ * 1. when a datastore becomes aliased. This can happen for both remote and local datastores
230
+ * 2. when a datastore is created locally as root
231
+ * 3. when a datastore is created locally as root and is rehydrated
232
+ * @returns whether a datastore is root in memory
233
+ */
234
+ protected isInMemoryRoot(): boolean {
235
+ return this._isInMemoryRoot;
236
+ }
237
+
238
+ protected registry: IFluidDataStoreRegistry | undefined;
239
+
240
+ protected detachedRuntimeCreation = false;
241
+ /** @deprecated - To be replaced by calling makeLocallyVisible directly */
242
+ public readonly bindToContext: () => void;
243
+ protected channel: IFluidDataStoreChannel | undefined;
244
+ private loaded = false;
245
+ protected pending: ISequencedDocumentMessage[] | undefined = [];
246
+ protected channelDeferred: Deferred<IFluidDataStoreChannel> | undefined;
247
+ private _baseSnapshot: ISnapshotTree | undefined;
248
+ protected _attachState: AttachState;
249
+ private _isInMemoryRoot: boolean = false;
250
+ protected readonly summarizerNode: ISummarizerNodeWithGC;
251
+ private readonly mc: MonitoringContext;
252
+ private readonly thresholdOpsCounter: ThresholdCounter;
253
+ private static readonly pendingOpsCountThreshold = 1000;
254
+
255
+ // The used routes of this node as per the last GC run. This is used to update the used routes of the channel
256
+ // if it realizes after GC is run.
257
+ private lastUsedRoutes: string[] | undefined;
258
+
259
+ public readonly id: string;
260
+ private readonly _containerRuntime: ContainerRuntime;
261
+ public readonly storage: IDocumentStorageService;
262
+ public readonly scope: FluidObject;
263
+ protected pkg?: readonly string[];
264
+
265
+ constructor(
266
+ props: IFluidDataStoreContextProps,
267
+ private readonly existing: boolean,
268
+ private bindState: BindState, // Used to assert for state tracking purposes
269
+ public readonly isLocalDataStore: boolean,
270
+ private readonly makeLocallyVisibleFn: () => void,
271
+ ) {
272
+ super();
273
+
274
+ this._containerRuntime = props.runtime;
275
+ this.id = props.id;
276
+ this.storage = props.storage;
277
+ this.scope = props.scope;
278
+ this.pkg = props.pkg;
279
+
280
+ // URIs use slashes as delimiters. Handles use URIs.
281
+ // Thus having slashes in types almost guarantees trouble down the road!
282
+ assert(!this.id.includes("/"), 0x13a /* Data store ID contains slash */);
283
+
284
+ this._attachState =
285
+ this.containerRuntime.attachState !== AttachState.Detached && this.existing
286
+ ? this.containerRuntime.attachState
287
+ : AttachState.Detached;
288
+
289
+ this.bindToContext = () => {
290
+ assert(
291
+ this.bindState === BindState.NotBound,
292
+ 0x13b /* "datastore context is already in bound state" */,
293
+ );
294
+ this.bindState = BindState.Binding;
295
+ assert(
296
+ this.channel !== undefined,
297
+ 0x13c /* "undefined channel on datastore context" */,
298
+ );
299
+ this.makeLocallyVisible();
300
+ this.bindState = BindState.Bound;
301
+ };
302
+
303
+ const thisSummarizeInternal = async (
304
+ fullTree: boolean,
305
+ trackState: boolean,
306
+ telemetryContext?: ITelemetryContext,
307
+ ) => this.summarizeInternal(fullTree, trackState, telemetryContext);
308
+
309
+ this.summarizerNode = props.createSummarizerNodeFn(
310
+ thisSummarizeInternal,
311
+ async (fullGC?: boolean) => this.getGCDataInternal(fullGC),
312
+ async () => this.getBaseGCDetails(),
313
+ );
314
+
315
+ this.mc = loggerToMonitoringContext(
316
+ ChildLogger.create(this.logger, "FluidDataStoreContext"),
317
+ );
318
+ this.thresholdOpsCounter = new ThresholdCounter(
319
+ FluidDataStoreContext.pendingOpsCountThreshold,
320
+ this.mc.logger,
321
+ );
322
+
323
+ // Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
324
+ this.throwOnTombstoneUsage =
325
+ this.mc.config.getBoolean(throwOnTombstoneUsageKey) === true &&
326
+ this.clientDetails.type !== summarizerClientType;
327
+ }
328
+
329
+ public dispose(): void {
330
+ if (this._disposed) {
331
+ return;
332
+ }
333
+ this._disposed = true;
334
+
335
+ // Dispose any pending runtime after it gets fulfilled
336
+ // Errors are logged where this.channelDeferred is consumed/generated (realizeCore(), bindRuntime())
337
+ if (this.channelDeferred) {
338
+ this.channelDeferred.promise
339
+ .then((runtime) => {
340
+ runtime.dispose();
341
+ })
342
+ .catch((error) => {});
343
+ }
344
+ }
345
+
346
+ public setTombstone(tombstone: boolean) {
347
+ if (this.tombstoned === tombstone) {
348
+ return;
349
+ }
350
+
351
+ this._tombstoned = tombstone;
352
+ }
353
+
354
+ private rejectDeferredRealize(reason: string, packageName?: string): never {
355
+ throw new LoggingError(reason, {
356
+ packageName: { value: packageName, tag: TelemetryDataTag.CodeArtifact },
357
+ });
358
+ }
359
+
360
+ public async realize(): Promise<IFluidDataStoreChannel> {
361
+ assert(!this.detachedRuntimeCreation, 0x13d /* "Detached runtime creation on realize()" */);
362
+ if (!this.channelDeferred) {
363
+ this.channelDeferred = new Deferred<IFluidDataStoreChannel>();
364
+ this.realizeCore(this.existing).catch((error) => {
365
+ const errorWrapped = DataProcessingError.wrapIfUnrecognized(
366
+ error,
367
+ "realizeFluidDataStoreContext",
368
+ );
369
+ errorWrapped.addTelemetryProperties({
370
+ fluidDataStoreId: {
371
+ value: this.id,
372
+ tag: TelemetryDataTag.CodeArtifact,
373
+ },
374
+ });
375
+ this.channelDeferred?.reject(errorWrapped);
376
+ this.logger.sendErrorEvent({ eventName: "RealizeError" }, errorWrapped);
377
+ });
378
+ }
379
+ return this.channelDeferred.promise;
380
+ }
381
+
382
+ protected async factoryFromPackagePath(packages?: readonly string[]) {
383
+ assert(this.pkg === packages, 0x13e /* "Unexpected package path" */);
384
+ if (packages === undefined) {
385
+ this.rejectDeferredRealize("packages is undefined");
386
+ }
387
+
388
+ let entry: FluidDataStoreRegistryEntry | undefined;
389
+ let registry: IFluidDataStoreRegistry | undefined =
390
+ this._containerRuntime.IFluidDataStoreRegistry;
391
+ let lastPkg: string | undefined;
392
+ for (const pkg of packages) {
393
+ if (!registry) {
394
+ this.rejectDeferredRealize("No registry for package", lastPkg);
395
+ }
396
+ lastPkg = pkg;
397
+ entry = await registry.get(pkg);
398
+ if (!entry) {
399
+ this.rejectDeferredRealize("Registry does not contain entry for the package", pkg);
400
+ }
401
+ registry = entry.IFluidDataStoreRegistry;
402
+ }
403
+ const factory = entry?.IFluidDataStoreFactory;
404
+ if (factory === undefined) {
405
+ this.rejectDeferredRealize("Can't find factory for package", lastPkg);
406
+ }
407
+
408
+ return { factory, registry };
409
+ }
410
+
411
+ private async realizeCore(existing: boolean): Promise<void> {
412
+ const details = await this.getInitialSnapshotDetails();
413
+ // Base snapshot is the baseline where pending ops are applied to.
414
+ // It is important that this be in sync with the pending ops, and also
415
+ // that it is set here, before bindRuntime is called.
416
+ this._baseSnapshot = details.snapshot;
417
+ const packages = details.pkg;
418
+
419
+ const { factory, registry } = await this.factoryFromPackagePath(packages);
420
+
421
+ assert(
422
+ this.registry === undefined,
423
+ 0x13f /* "datastore context registry is already set" */,
424
+ );
425
+ this.registry = registry;
426
+
427
+ const channel = await factory.instantiateDataStore(this, existing);
428
+ assert(channel !== undefined, 0x140 /* "undefined channel on datastore context" */);
429
+ this.bindRuntime(channel);
430
+ }
431
+
432
+ /**
433
+ * Notifies this object about changes in the connection state.
434
+ * @param value - New connection state.
435
+ * @param clientId - ID of the client. Its old ID when in disconnected state and
436
+ * its new client ID when we are connecting or connected.
437
+ */
438
+ public setConnectionState(connected: boolean, clientId?: string) {
439
+ // ConnectionState should not fail in tombstone mode as this is internally run
440
+ this.verifyNotClosed("setConnectionState", false /* checkTombstone */);
441
+
442
+ // Connection events are ignored if the store is not yet loaded
443
+ if (!this.loaded) {
444
+ return;
445
+ }
446
+
447
+ assert(this.connected === connected, 0x141 /* "Unexpected connected state" */);
448
+
449
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
450
+ this.channel!.setConnectionState(connected, clientId);
451
+ }
452
+
453
+ public process(
454
+ messageArg: ISequencedDocumentMessage,
455
+ local: boolean,
456
+ localOpMetadata: unknown,
457
+ ): void {
458
+ this.verifyNotClosed("process", true, extractSafePropertiesFromMessage(messageArg));
459
+
460
+ const innerContents = messageArg.contents as FluidDataStoreMessage;
461
+ const message = {
462
+ ...messageArg,
463
+ type: innerContents.type,
464
+ contents: innerContents.content,
465
+ };
466
+
467
+ this.summarizerNode.recordChange(message);
468
+
469
+ if (this.loaded) {
470
+ return this.channel?.process(message, local, localOpMetadata);
471
+ } else {
472
+ assert(!local, 0x142 /* "local store channel is not loaded" */);
473
+ assert(this.pending !== undefined, 0x23d /* "pending is undefined" */);
474
+ this.pending.push(message);
475
+ this.thresholdOpsCounter.sendIfMultiple("StorePendingOps", this.pending.length);
476
+ }
477
+ }
478
+
479
+ public processSignal(message: IInboundSignalMessage, local: boolean): void {
480
+ this.verifyNotClosed("processSignal");
481
+
482
+ // Signals are ignored if the store is not yet loaded
483
+ if (!this.loaded) {
484
+ return;
485
+ }
486
+
487
+ this.channel?.processSignal(message, local);
488
+ }
489
+
490
+ public getQuorum(): IQuorumClients {
491
+ return this._containerRuntime.getQuorum();
492
+ }
493
+
494
+ public getAudience(): IAudience {
495
+ return this._containerRuntime.getAudience();
496
+ }
497
+
498
+ /**
499
+ * Returns a summary at the current sequence number.
500
+ * @param fullTree - true to bypass optimizations and force a full summary tree
501
+ * @param trackState - This tells whether we should track state from this summary.
502
+ * @param telemetryContext - summary data passed through the layers for telemetry purposes
503
+ */
504
+ public async summarize(
505
+ fullTree: boolean = false,
506
+ trackState: boolean = true,
507
+ telemetryContext?: ITelemetryContext,
508
+ ): Promise<ISummarizeResult> {
509
+ return this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
510
+ }
511
+
512
+ private async summarizeInternal(
513
+ fullTree: boolean,
514
+ trackState: boolean,
515
+ telemetryContext?: ITelemetryContext,
516
+ ): Promise<ISummarizeInternalResult> {
517
+ await this.realize();
518
+
519
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
520
+ const summarizeResult = await this.channel!.summarize(
521
+ fullTree,
522
+ trackState,
523
+ telemetryContext,
524
+ );
525
+
526
+ // Wrap dds summaries in .channels subtree.
527
+ wrapSummaryInChannelsTree(summarizeResult);
528
+ const pathPartsForChildren = [channelsTreeName];
529
+
530
+ // Add data store's attributes to the summary.
531
+ const { pkg } = await this.getInitialSnapshotDetails();
532
+ const isRoot = await this.isRoot();
533
+ const attributes = createAttributes(pkg, isRoot);
534
+ addBlobToSummary(summarizeResult, dataStoreAttributesBlobName, JSON.stringify(attributes));
535
+
536
+ // If we are not referenced, mark the summary tree as unreferenced. Also, update unreferenced blob
537
+ // size in the summary stats with the blobs size of this data store.
538
+ if (!this.summarizerNode.isReferenced()) {
539
+ summarizeResult.summary.unreferenced = true;
540
+ summarizeResult.stats.unreferencedBlobSize = summarizeResult.stats.totalBlobSize;
541
+ }
542
+
543
+ return {
544
+ ...summarizeResult,
545
+ id: this.id,
546
+ pathPartsForChildren,
547
+ };
548
+ }
549
+
550
+ /**
551
+ * Returns the data used for garbage collection. This includes a list of GC nodes that represent this data store
552
+ * including any of its child channel contexts. Each node has a set of outbound routes to other GC nodes in the
553
+ * document.
554
+ * If there is no new data in this data store since the last summary, previous GC data is used.
555
+ * If there is new data, the GC data is generated again (by calling getGCDataInternal).
556
+ * @param fullGC - true to bypass optimizations and force full generation of GC data.
557
+ */
558
+ public async getGCData(fullGC: boolean = false): Promise<IGarbageCollectionData> {
559
+ return this.summarizerNode.getGCData(fullGC);
560
+ }
561
+
562
+ /**
563
+ * Generates data used for garbage collection. This is called when there is new data since last summary. It
564
+ * realizes the data store and calls into each channel context to get its GC data.
565
+ * @param fullGC - true to bypass optimizations and force full generation of GC data.
566
+ */
567
+ private async getGCDataInternal(fullGC: boolean = false): Promise<IGarbageCollectionData> {
568
+ await this.realize();
569
+ assert(
570
+ this.channel !== undefined,
571
+ 0x143 /* "Channel should not be undefined when running GC" */,
572
+ );
573
+
574
+ return this.channel.getGCData(fullGC);
575
+ }
576
+
577
+ /**
578
+ * After GC has run, called to notify the data store of routes used in it. These are used for the following:
579
+ *
580
+ * 1. To identify if this data store is being referenced in the document or not.
581
+ *
582
+ * 2. To determine if it needs to re-summarize in case used routes changed since last summary.
583
+ *
584
+ * 3. These are added to the summary generated by the data store.
585
+ *
586
+ * 4. To notify child contexts of their used routes. This is done immediately if the data store is loaded.
587
+ * Else, it is done when realizing the data store.
588
+ *
589
+ * 5. To update the timestamp when this data store or any children are marked as unreferenced.
590
+ *
591
+ * @param usedRoutes - The routes that are used in this data store.
592
+ */
593
+ public updateUsedRoutes(usedRoutes: string[]) {
594
+ // Update the used routes in this data store's summarizer node.
595
+ this.summarizerNode.updateUsedRoutes(usedRoutes);
596
+
597
+ /**
598
+ * Store the used routes to update the channel if the data store is not loaded yet. If the used routes changed
599
+ * since the previous run, the data store will be loaded during summarize since the used state changed. So, it's
600
+ * safe to only store the last used routes.
601
+ */
602
+ this.lastUsedRoutes = usedRoutes;
603
+
604
+ // If we are loaded, call the channel so it can update the used routes of the child contexts.
605
+ // If we are not loaded, we will update this when we are realized.
606
+ if (this.loaded) {
607
+ this.updateChannelUsedRoutes();
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Called when a new outbound reference is added to another node. This is used by garbage collection to identify
613
+ * all references added in the system.
614
+ * @param srcHandle - The handle of the node that added the reference.
615
+ * @param outboundHandle - The handle of the outbound node that is referenced.
616
+ */
617
+ public addedGCOutboundReference(srcHandle: IFluidHandle, outboundHandle: IFluidHandle) {
618
+ this._containerRuntime.addedGCOutboundReference(srcHandle, outboundHandle);
619
+ }
620
+
621
+ /**
622
+ * Updates the used routes of the channel and its child contexts. The channel must be loaded before calling this.
623
+ * It is called in these two scenarios:
624
+ * 1. When the used routes of the data store is updated and the data store is loaded.
625
+ * 2. When the data store is realized. This updates the channel's used routes as per last GC run.
626
+ */
627
+ private updateChannelUsedRoutes() {
628
+ assert(this.loaded, 0x144 /* "Channel should be loaded when updating used routes" */);
629
+ assert(
630
+ this.channel !== undefined,
631
+ 0x145 /* "Channel should be present when data store is loaded" */,
632
+ );
633
+
634
+ // If there is no lastUsedRoutes, GC has not run up until this point.
635
+ if (this.lastUsedRoutes === undefined) {
636
+ return;
637
+ }
638
+
639
+ // Remove the route to this data store, if it exists.
640
+ const usedChannelRoutes = this.lastUsedRoutes.filter((id: string) => {
641
+ return id !== "/" && id !== "";
642
+ });
643
+ this.channel.updateUsedRoutes(usedChannelRoutes);
644
+ }
645
+
646
+ /**
647
+ * @deprecated 0.18.Should call request on the runtime directly
648
+ */
649
+ public async request(request: IRequest): Promise<IResponse> {
650
+ const runtime = await this.realize();
651
+ return runtime.request(request);
652
+ }
653
+
654
+ public submitMessage(type: string, content: any, localOpMetadata: unknown): void {
655
+ this.verifyNotClosed("submitMessage");
656
+ assert(!!this.channel, 0x146 /* "Channel must exist when submitting message" */);
657
+ const fluidDataStoreContent: FluidDataStoreMessage = {
658
+ content,
659
+ type,
660
+ };
661
+ this._containerRuntime.submitDataStoreOp(this.id, fluidDataStoreContent, localOpMetadata);
662
+ }
663
+
664
+ /**
665
+ * This is called from a SharedSummaryBlock that does not generate ops but only wants to be part of the summary.
666
+ * It indicates that there is data in the object that needs to be summarized.
667
+ * We will update the latestSequenceNumber of the summary tracker of this
668
+ * store and of the object's channel.
669
+ *
670
+ * @param address - The address of the channel that is dirty.
671
+ *
672
+ */
673
+ public setChannelDirty(address: string): void {
674
+ this.verifyNotClosed("setChannelDirty");
675
+
676
+ // Get the latest sequence number.
677
+ const latestSequenceNumber = this.deltaManager.lastSequenceNumber;
678
+
679
+ this.summarizerNode.invalidate(latestSequenceNumber);
680
+
681
+ const channelSummarizerNode = this.summarizerNode.getChild(address);
682
+
683
+ if (channelSummarizerNode) {
684
+ channelSummarizerNode.invalidate(latestSequenceNumber); // TODO: lazy load problem?
685
+ }
686
+ }
687
+
688
+ public submitSignal(type: string, content: any) {
689
+ this.verifyNotClosed("submitSignal");
690
+
691
+ assert(!!this.channel, 0x147 /* "Channel must exist on submitting signal" */);
692
+ return this._containerRuntime.submitDataStoreSignal(this.id, type, content);
693
+ }
694
+
695
+ /**
696
+ * This is called by the data store channel when it becomes locally visible indicating that it is ready to become
697
+ * globally visible now.
698
+ */
699
+ public makeLocallyVisible() {
700
+ assert(this.channel !== undefined, 0x2cf /* "undefined channel on datastore context" */);
701
+ this.makeLocallyVisibleFn();
702
+ }
703
+
704
+ protected bindRuntime(channel: IFluidDataStoreChannel) {
705
+ if (this.channel) {
706
+ throw new Error("Runtime already bound");
707
+ }
708
+
709
+ try {
710
+ assert(
711
+ !this.detachedRuntimeCreation,
712
+ 0x148 /* "Detached runtime creation on runtime bind" */,
713
+ );
714
+ assert(this.channelDeferred !== undefined, 0x149 /* "Undefined channel deferral" */);
715
+ assert(this.pkg !== undefined, 0x14a /* "Undefined package path" */);
716
+
717
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
718
+ const pending = this.pending!;
719
+
720
+ // Apply all pending ops
721
+ for (const op of pending) {
722
+ channel.process(op, false, undefined /* localOpMetadata */);
723
+ }
724
+
725
+ this.thresholdOpsCounter.send("ProcessPendingOps", pending.length);
726
+ this.pending = undefined;
727
+
728
+ // And now mark the runtime active
729
+ this.loaded = true;
730
+ this.channel = channel;
731
+
732
+ // Freeze the package path to ensure that someone doesn't modify it when it is
733
+ // returned in packagePath().
734
+ Object.freeze(this.pkg);
735
+
736
+ /**
737
+ * Update the used routes of the channel. If GC has run before this data store was realized, we will have
738
+ * the used routes saved. So, this will ensure that all the child contexts have up-to-date used routes as
739
+ * per the last time GC was run.
740
+ * Also, this data store may have been realized during summarize. In that case, the child contexts need to
741
+ * have their used routes updated to determine if its needs to summarize again and to add it to the summary.
742
+ */
743
+ this.updateChannelUsedRoutes();
744
+
745
+ // And notify the pending promise it is now available
746
+ this.channelDeferred.resolve(this.channel);
747
+ } catch (error) {
748
+ this.channelDeferred?.reject(error);
749
+ this.logger.sendErrorEvent(
750
+ {
751
+ eventName: "BindRuntimeError",
752
+ fluidDataStoreId: {
753
+ value: this.id,
754
+ tag: TelemetryDataTag.CodeArtifact,
755
+ },
756
+ },
757
+ error,
758
+ );
759
+ }
760
+ }
761
+
762
+ public async getAbsoluteUrl(relativeUrl: string): Promise<string | undefined> {
763
+ if (this.attachState !== AttachState.Attached) {
764
+ return undefined;
765
+ }
766
+ return this._containerRuntime.getAbsoluteUrl(relativeUrl);
767
+ }
768
+
769
+ public abstract generateAttachMessage(): IAttachMessage;
770
+
771
+ public abstract getInitialSnapshotDetails(): Promise<ISnapshotDetails>;
772
+
773
+ /**
774
+ * @deprecated Sets the datastore as root, for aliasing purposes: #7948
775
+ * This method should not be used outside of the aliasing context.
776
+ * It will be removed, as the source of truth for this flag will be the aliasing blob.
777
+ */
778
+ public setInMemoryRoot(): void {
779
+ this._isInMemoryRoot = true;
780
+ }
781
+
782
+ public abstract getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase>;
783
+
784
+ public reSubmit(contents: any, localOpMetadata: unknown) {
785
+ assert(!!this.channel, 0x14b /* "Channel must exist when resubmitting ops" */);
786
+ const innerContents = contents as FluidDataStoreMessage;
787
+ this.channel.reSubmit(innerContents.type, innerContents.content, localOpMetadata);
788
+ }
789
+
790
+ public rollback(contents: any, localOpMetadata: unknown) {
791
+ if (!this.channel) {
792
+ throw new Error("Channel must exist when rolling back ops");
793
+ }
794
+ if (!this.channel.rollback) {
795
+ throw new Error("Channel doesn't support rollback");
796
+ }
797
+ const innerContents = contents as FluidDataStoreMessage;
798
+ this.channel.rollback(innerContents.type, innerContents.content, localOpMetadata);
799
+ }
800
+
801
+ public async applyStashedOp(contents: any): Promise<unknown> {
802
+ if (!this.channel) {
803
+ await this.realize();
804
+ }
805
+ assert(!!this.channel, 0x14c /* "Channel must exist when rebasing ops" */);
806
+ const innerContents = contents as FluidDataStoreMessage;
807
+ return this.channel.applyStashedOp(innerContents.content);
808
+ }
809
+
810
+ private verifyNotClosed(
811
+ callSite: string,
812
+ checkTombstone = true,
813
+ safeTelemetryProps: ITelemetryProperties = {},
814
+ ) {
815
+ if (this._disposed) {
816
+ throw new Error(`Context is closed! Call site [${callSite}]`);
817
+ }
818
+
819
+ if (checkTombstone && this.tombstoned) {
820
+ const messageString = `Context is tombstoned! Call site [${callSite}]`;
821
+ const error = new DataCorruptionError(messageString, safeTelemetryProps);
822
+
823
+ sendGCUnexpectedUsageEvent(
824
+ this.mc,
825
+ {
826
+ eventName: "GC_Tombstone_DataStore_Changed",
827
+ category: this.throwOnTombstoneUsage ? "error" : "generic",
828
+ isSummarizerClient: this.clientDetails.type === summarizerClientType,
829
+ callSite,
830
+ },
831
+ this.pkg,
832
+ error,
833
+ );
834
+ if (this.throwOnTombstoneUsage) {
835
+ throw error;
836
+ }
837
+ }
838
+ }
839
+
840
+ public getCreateChildSummarizerNodeFn(id: string, createParam: CreateChildSummarizerNodeParam) {
841
+ return (
842
+ summarizeInternal: SummarizeInternalFn,
843
+ getGCDataFn: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
844
+ getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
845
+ ) =>
846
+ this.summarizerNode.createChild(
847
+ summarizeInternal,
848
+ id,
849
+ createParam,
850
+ // DDS will not create failure summaries
851
+ { throwOnFailure: true },
852
+ getGCDataFn,
853
+ getBaseGCDetailsFn,
854
+ );
855
+ }
856
+
857
+ public async uploadBlob(blob: ArrayBufferLike): Promise<IFluidHandle<ArrayBufferLike>> {
858
+ return this.containerRuntime.uploadBlob(blob);
859
+ }
817
860
  }
818
861
 
819
862
  export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
820
- private readonly initSnapshotValue: ISnapshotTree | undefined;
821
- private readonly baseGCDetailsP: Promise<IGarbageCollectionDetailsBase>;
822
-
823
- constructor(props: IRemoteFluidDataStoreContextProps) {
824
- super(
825
- props,
826
- true /* existing */,
827
- BindState.Bound,
828
- false /* isLocalDataStore */,
829
- () => {
830
- throw new Error("Already attached");
831
- },
832
- );
833
-
834
- this.initSnapshotValue = props.snapshotTree;
835
- this.baseGCDetailsP = new LazyPromise<IGarbageCollectionDetailsBase>(async () => {
836
- return (await props.getBaseGCDetails()) ?? {};
837
- });
838
-
839
- if (props.snapshotTree !== undefined) {
840
- this.summarizerNode.updateBaseSummaryState(props.snapshotTree);
841
- }
842
- }
843
-
844
- private readonly initialSnapshotDetailsP = new LazyPromise<ISnapshotDetails>(async () => {
845
- let tree = this.initSnapshotValue;
846
- let isRootDataStore = true;
847
-
848
- if (!!tree && tree.blobs[dataStoreAttributesBlobName] !== undefined) {
849
- // Need to get through snapshot and use that to populate extraBlobs
850
- const attributes =
851
- await readAndParse<ReadFluidDataStoreAttributes>(this.storage, tree.blobs[dataStoreAttributesBlobName]);
852
-
853
- let pkgFromSnapshot: string[];
854
- // Use the snapshotFormatVersion to determine how the pkg is encoded in the snapshot.
855
- // For snapshotFormatVersion = "0.1" (1) or above, pkg is jsonified, otherwise it is just a string.
856
- const formatVersion = getAttributesFormatVersion(attributes);
857
- if (formatVersion < 1) {
858
- pkgFromSnapshot = attributes.pkg.startsWith("[\"") && attributes.pkg.endsWith("\"]")
859
- ? JSON.parse(attributes.pkg) as string[]
860
- : [attributes.pkg];
861
- } else {
862
- pkgFromSnapshot = JSON.parse(attributes.pkg) as string[];
863
- }
864
- this.pkg = pkgFromSnapshot;
865
-
866
- /**
867
- * If there is no isRootDataStore in the attributes blob, set it to true. This will ensure that
868
- * data stores in older documents are not garbage collected incorrectly. This may lead to additional
869
- * roots in the document but they won't break.
870
- */
871
- isRootDataStore = attributes.isRootDataStore ?? true;
872
-
873
- if (hasIsolatedChannels(attributes)) {
874
- tree = tree.trees[channelsTreeName];
875
- assert(tree !== undefined,
876
- 0x1fe /* "isolated channels subtree should exist in remote datastore snapshot" */);
877
- }
878
- }
879
-
880
- return {
881
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
882
- pkg: this.pkg!,
883
- isRootDataStore,
884
- snapshot: tree,
885
- };
886
- });
887
-
888
- public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
889
- return this.initialSnapshotDetailsP;
890
- }
891
-
892
- public async getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase> {
893
- return this.baseGCDetailsP;
894
- }
895
-
896
- public generateAttachMessage(): IAttachMessage {
897
- throw new Error("Cannot attach remote store");
898
- }
863
+ private readonly initSnapshotValue: ISnapshotTree | undefined;
864
+ private readonly baseGCDetailsP: Promise<IGarbageCollectionDetailsBase>;
865
+
866
+ constructor(props: IRemoteFluidDataStoreContextProps) {
867
+ super(props, true /* existing */, BindState.Bound, false /* isLocalDataStore */, () => {
868
+ throw new Error("Already attached");
869
+ });
870
+
871
+ this.initSnapshotValue = props.snapshotTree;
872
+ this.baseGCDetailsP = new LazyPromise<IGarbageCollectionDetailsBase>(async () => {
873
+ return (await props.getBaseGCDetails()) ?? {};
874
+ });
875
+
876
+ if (props.snapshotTree !== undefined) {
877
+ this.summarizerNode.updateBaseSummaryState(props.snapshotTree);
878
+ }
879
+ }
880
+
881
+ private readonly initialSnapshotDetailsP = new LazyPromise<ISnapshotDetails>(async () => {
882
+ let tree = this.initSnapshotValue;
883
+ let isRootDataStore = true;
884
+
885
+ if (!!tree && tree.blobs[dataStoreAttributesBlobName] !== undefined) {
886
+ // Need to get through snapshot and use that to populate extraBlobs
887
+ const attributes = await readAndParse<ReadFluidDataStoreAttributes>(
888
+ this.storage,
889
+ tree.blobs[dataStoreAttributesBlobName],
890
+ );
891
+
892
+ let pkgFromSnapshot: string[];
893
+ // Use the snapshotFormatVersion to determine how the pkg is encoded in the snapshot.
894
+ // For snapshotFormatVersion = "0.1" (1) or above, pkg is jsonified, otherwise it is just a string.
895
+ const formatVersion = getAttributesFormatVersion(attributes);
896
+ if (formatVersion < 1) {
897
+ pkgFromSnapshot =
898
+ attributes.pkg.startsWith('["') && attributes.pkg.endsWith('"]')
899
+ ? (JSON.parse(attributes.pkg) as string[])
900
+ : [attributes.pkg];
901
+ } else {
902
+ pkgFromSnapshot = JSON.parse(attributes.pkg) as string[];
903
+ }
904
+ this.pkg = pkgFromSnapshot;
905
+
906
+ /**
907
+ * If there is no isRootDataStore in the attributes blob, set it to true. This will ensure that
908
+ * data stores in older documents are not garbage collected incorrectly. This may lead to additional
909
+ * roots in the document but they won't break.
910
+ */
911
+ isRootDataStore = attributes.isRootDataStore ?? true;
912
+
913
+ if (hasIsolatedChannels(attributes)) {
914
+ tree = tree.trees[channelsTreeName];
915
+ assert(
916
+ tree !== undefined,
917
+ 0x1fe /* "isolated channels subtree should exist in remote datastore snapshot" */,
918
+ );
919
+ }
920
+ }
921
+
922
+ return {
923
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
924
+ pkg: this.pkg!,
925
+ isRootDataStore,
926
+ snapshot: tree,
927
+ };
928
+ });
929
+
930
+ public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
931
+ return this.initialSnapshotDetailsP;
932
+ }
933
+
934
+ public async getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase> {
935
+ return this.baseGCDetailsP;
936
+ }
937
+
938
+ public generateAttachMessage(): IAttachMessage {
939
+ throw new Error("Cannot attach remote store");
940
+ }
899
941
  }
900
942
 
901
943
  /**
902
944
  * Base class for detached & attached context classes
903
945
  */
904
946
  export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
905
- private readonly snapshotTree: ISnapshotTree | undefined;
906
- /**
907
- * @deprecated 0.16 Issue #1635, #3631
908
- */
909
- public readonly createProps?: any;
910
-
911
- constructor(props: ILocalFluidDataStoreContextProps) {
912
- super(
913
- props,
914
- props.snapshotTree !== undefined ? true : false /* existing */,
915
- props.snapshotTree ? BindState.Bound : BindState.NotBound,
916
- true /* isLocalDataStore */,
917
- props.makeLocallyVisibleFn,
918
- );
919
-
920
- this.snapshotTree = props.snapshotTree;
921
- if (props.isRootDataStore === true) {
922
- this.setInMemoryRoot();
923
- }
924
- this.createProps = props.createProps;
925
- this.attachListeners();
926
- }
927
-
928
- private attachListeners(): void {
929
- this.once("attaching", () => {
930
- assert(this.attachState === AttachState.Detached, 0x14d /* "Should move from detached to attaching" */);
931
- this._attachState = AttachState.Attaching;
932
- });
933
- this.once("attached", () => {
934
- assert(this.attachState === AttachState.Attaching, 0x14e /* "Should move from attaching to attached" */);
935
- this._attachState = AttachState.Attached;
936
- });
937
- }
938
-
939
- public generateAttachMessage(): IAttachMessage {
940
- assert(this.channel !== undefined, 0x14f /* "There should be a channel when generating attach message" */);
941
- assert(this.pkg !== undefined, 0x150 /* "pkg should be available in local data store context" */);
942
-
943
- const summarizeResult = this.channel.getAttachSummary();
944
-
945
- // Wrap dds summaries in .channels subtree.
946
- wrapSummaryInChannelsTree(summarizeResult);
947
-
948
- // Add data store's attributes to the summary.
949
- const attributes = createAttributes(
950
- this.pkg,
951
- this.isInMemoryRoot(),
952
- );
953
- addBlobToSummary(summarizeResult, dataStoreAttributesBlobName, JSON.stringify(attributes));
954
-
955
- // Attach message needs the summary in ITree format. Convert the ISummaryTree into an ITree.
956
- const snapshot = convertSummaryTreeToITree(summarizeResult.summary);
957
-
958
- const message: IAttachMessage = {
959
- id: this.id,
960
- snapshot,
961
- type: this.pkg[this.pkg.length - 1],
962
- };
963
-
964
- return message;
965
- }
966
-
967
- public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
968
- let snapshot = this.snapshotTree;
969
- let attributes: ReadFluidDataStoreAttributes;
970
- let isRootDataStore = false;
971
- if (snapshot !== undefined) {
972
- // Get the dataStore attributes.
973
- // Note: storage can be undefined in special case while detached.
974
- attributes = await getFluidDataStoreAttributes(this.storage, snapshot);
975
- if (hasIsolatedChannels(attributes)) {
976
- snapshot = snapshot.trees[channelsTreeName];
977
- assert(snapshot !== undefined,
978
- 0x1ff /* "isolated channels subtree should exist in local datastore snapshot" */);
979
- }
980
- if (this.pkg === undefined) {
981
- this.pkg = JSON.parse(attributes.pkg) as string[];
982
- // If there is no isRootDataStore in the attributes blob, set it to true. This ensures that data
983
- // stores in older documents are not garbage collected incorrectly. This may lead to additional
984
- // roots in the document but they won't break.
985
- if (attributes.isRootDataStore ?? true) {
986
- isRootDataStore = true;
987
- this.setInMemoryRoot();
988
- }
989
- }
990
- }
991
- assert(this.pkg !== undefined, 0x152 /* "pkg should be available in local data store" */);
992
-
993
- return {
994
- pkg: this.pkg,
995
- isRootDataStore,
996
- snapshot,
997
- };
998
- }
999
-
1000
- public async getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase> {
1001
- // Local data store does not have initial summary.
1002
- return {};
1003
- }
947
+ private readonly snapshotTree: ISnapshotTree | undefined;
948
+ /**
949
+ * @deprecated 0.16 Issue #1635, #3631
950
+ */
951
+ public readonly createProps?: any;
952
+
953
+ constructor(props: ILocalFluidDataStoreContextProps) {
954
+ super(
955
+ props,
956
+ props.snapshotTree !== undefined ? true : false /* existing */,
957
+ props.snapshotTree ? BindState.Bound : BindState.NotBound,
958
+ true /* isLocalDataStore */,
959
+ props.makeLocallyVisibleFn,
960
+ );
961
+
962
+ this.snapshotTree = props.snapshotTree;
963
+ if (props.isRootDataStore === true) {
964
+ this.setInMemoryRoot();
965
+ }
966
+ this.createProps = props.createProps;
967
+ this.attachListeners();
968
+ }
969
+
970
+ private attachListeners(): void {
971
+ this.once("attaching", () => {
972
+ assert(
973
+ this.attachState === AttachState.Detached,
974
+ 0x14d /* "Should move from detached to attaching" */,
975
+ );
976
+ this._attachState = AttachState.Attaching;
977
+ });
978
+ this.once("attached", () => {
979
+ assert(
980
+ this.attachState === AttachState.Attaching,
981
+ 0x14e /* "Should move from attaching to attached" */,
982
+ );
983
+ this._attachState = AttachState.Attached;
984
+ });
985
+ }
986
+
987
+ public generateAttachMessage(): IAttachMessage {
988
+ assert(
989
+ this.channel !== undefined,
990
+ 0x14f /* "There should be a channel when generating attach message" */,
991
+ );
992
+ assert(
993
+ this.pkg !== undefined,
994
+ 0x150 /* "pkg should be available in local data store context" */,
995
+ );
996
+
997
+ const summarizeResult = this.channel.getAttachSummary();
998
+
999
+ // Wrap dds summaries in .channels subtree.
1000
+ wrapSummaryInChannelsTree(summarizeResult);
1001
+
1002
+ // Add data store's attributes to the summary.
1003
+ const attributes = createAttributes(this.pkg, this.isInMemoryRoot());
1004
+ addBlobToSummary(summarizeResult, dataStoreAttributesBlobName, JSON.stringify(attributes));
1005
+
1006
+ // Attach message needs the summary in ITree format. Convert the ISummaryTree into an ITree.
1007
+ const snapshot = convertSummaryTreeToITree(summarizeResult.summary);
1008
+
1009
+ const message: IAttachMessage = {
1010
+ id: this.id,
1011
+ snapshot,
1012
+ type: this.pkg[this.pkg.length - 1],
1013
+ };
1014
+
1015
+ return message;
1016
+ }
1017
+
1018
+ public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
1019
+ let snapshot = this.snapshotTree;
1020
+ let attributes: ReadFluidDataStoreAttributes;
1021
+ let isRootDataStore = false;
1022
+ if (snapshot !== undefined) {
1023
+ // Get the dataStore attributes.
1024
+ // Note: storage can be undefined in special case while detached.
1025
+ attributes = await getFluidDataStoreAttributes(this.storage, snapshot);
1026
+ if (hasIsolatedChannels(attributes)) {
1027
+ snapshot = snapshot.trees[channelsTreeName];
1028
+ assert(
1029
+ snapshot !== undefined,
1030
+ 0x1ff /* "isolated channels subtree should exist in local datastore snapshot" */,
1031
+ );
1032
+ }
1033
+ if (this.pkg === undefined) {
1034
+ this.pkg = JSON.parse(attributes.pkg) as string[];
1035
+ // If there is no isRootDataStore in the attributes blob, set it to true. This ensures that data
1036
+ // stores in older documents are not garbage collected incorrectly. This may lead to additional
1037
+ // roots in the document but they won't break.
1038
+ if (attributes.isRootDataStore ?? true) {
1039
+ isRootDataStore = true;
1040
+ this.setInMemoryRoot();
1041
+ }
1042
+ }
1043
+ }
1044
+ assert(this.pkg !== undefined, 0x152 /* "pkg should be available in local data store" */);
1045
+
1046
+ return {
1047
+ pkg: this.pkg,
1048
+ isRootDataStore,
1049
+ snapshot,
1050
+ };
1051
+ }
1052
+
1053
+ public async getBaseGCDetails(): Promise<IGarbageCollectionDetailsBase> {
1054
+ // Local data store does not have initial summary.
1055
+ return {};
1056
+ }
1004
1057
  }
1005
1058
 
1006
1059
  /**
@@ -1010,9 +1063,9 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
1010
1063
  * Runtime is created using data store factory that is associated with this context.
1011
1064
  */
1012
1065
  export class LocalFluidDataStoreContext extends LocalFluidDataStoreContextBase {
1013
- constructor(props: ILocalFluidDataStoreContextProps) {
1014
- super(props);
1015
- }
1066
+ constructor(props: ILocalFluidDataStoreContextProps) {
1067
+ super(props);
1068
+ }
1016
1069
  }
1017
1070
 
1018
1071
  /**
@@ -1022,50 +1075,54 @@ export class LocalFluidDataStoreContext extends LocalFluidDataStoreContextBase {
1022
1075
  * Attachment process provides all missing parts - package path, data store runtime, and data store factory
1023
1076
  */
1024
1077
  export class LocalDetachedFluidDataStoreContext
1025
- extends LocalFluidDataStoreContextBase
1026
- implements IFluidDataStoreContextDetached {
1027
- constructor(props: ILocalFluidDataStoreContextProps) {
1028
- super(props);
1029
- this.detachedRuntimeCreation = true;
1030
- }
1031
-
1032
- public async attachRuntime(
1033
- registry: IProvideFluidDataStoreFactory,
1034
- dataStoreChannel: IFluidDataStoreChannel) {
1035
- assert(this.detachedRuntimeCreation, 0x154 /* "runtime creation is already attached" */);
1036
- this.detachedRuntimeCreation = false;
1037
-
1038
- assert(this.channelDeferred === undefined, 0x155 /* "channel deferral is already set" */);
1039
- this.channelDeferred = new Deferred<IFluidDataStoreChannel>();
1040
-
1041
- const factory = registry.IFluidDataStoreFactory;
1042
-
1043
- const entry = await this.factoryFromPackagePath(this.pkg);
1044
- assert(entry.factory === factory, 0x156 /* "Unexpected factory for package path" */);
1045
-
1046
- assert(this.registry === undefined, 0x157 /* "datastore registry already attached" */);
1047
- this.registry = entry.registry;
1048
-
1049
- super.bindRuntime(dataStoreChannel);
1050
-
1051
- // Load the handle to the data store's entryPoint to make sure that for a detached data store, the entryPoint
1052
- // initialization function is called before the data store gets attached and potentially connected to the
1053
- // delta stream, so it gets a chance to do things while the data store is still "purely local".
1054
- // This preserves the behavior from before we introduced entryPoints, where the instantiateDataStore method
1055
- // of data store factories tends to construct the data object (at least kick off an async method that returns
1056
- // it); that code moved to the entryPoint initialization function, so we want to ensure it still executes
1057
- // before the data store is attached.
1058
- await dataStoreChannel.entryPoint?.get();
1059
-
1060
- if (await this.isRoot()) {
1061
- dataStoreChannel.makeVisibleAndAttachGraph();
1062
- }
1063
- }
1064
-
1065
- public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
1066
- if (this.detachedRuntimeCreation) {
1067
- throw new Error("Detached Fluid Data Store context can't be realized! Please attach runtime first!");
1068
- }
1069
- return super.getInitialSnapshotDetails();
1070
- }
1078
+ extends LocalFluidDataStoreContextBase
1079
+ implements IFluidDataStoreContextDetached
1080
+ {
1081
+ constructor(props: ILocalFluidDataStoreContextProps) {
1082
+ super(props);
1083
+ this.detachedRuntimeCreation = true;
1084
+ }
1085
+
1086
+ public async attachRuntime(
1087
+ registry: IProvideFluidDataStoreFactory,
1088
+ dataStoreChannel: IFluidDataStoreChannel,
1089
+ ) {
1090
+ assert(this.detachedRuntimeCreation, 0x154 /* "runtime creation is already attached" */);
1091
+ this.detachedRuntimeCreation = false;
1092
+
1093
+ assert(this.channelDeferred === undefined, 0x155 /* "channel deferral is already set" */);
1094
+ this.channelDeferred = new Deferred<IFluidDataStoreChannel>();
1095
+
1096
+ const factory = registry.IFluidDataStoreFactory;
1097
+
1098
+ const entry = await this.factoryFromPackagePath(this.pkg);
1099
+ assert(entry.factory === factory, 0x156 /* "Unexpected factory for package path" */);
1100
+
1101
+ assert(this.registry === undefined, 0x157 /* "datastore registry already attached" */);
1102
+ this.registry = entry.registry;
1103
+
1104
+ super.bindRuntime(dataStoreChannel);
1105
+
1106
+ // Load the handle to the data store's entryPoint to make sure that for a detached data store, the entryPoint
1107
+ // initialization function is called before the data store gets attached and potentially connected to the
1108
+ // delta stream, so it gets a chance to do things while the data store is still "purely local".
1109
+ // This preserves the behavior from before we introduced entryPoints, where the instantiateDataStore method
1110
+ // of data store factories tends to construct the data object (at least kick off an async method that returns
1111
+ // it); that code moved to the entryPoint initialization function, so we want to ensure it still executes
1112
+ // before the data store is attached.
1113
+ await dataStoreChannel.entryPoint?.get();
1114
+
1115
+ if (await this.isRoot()) {
1116
+ dataStoreChannel.makeVisibleAndAttachGraph();
1117
+ }
1118
+ }
1119
+
1120
+ public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
1121
+ if (this.detachedRuntimeCreation) {
1122
+ throw new Error(
1123
+ "Detached Fluid Data Store context can't be realized! Please attach runtime first!",
1124
+ );
1125
+ }
1126
+ return super.getInitialSnapshotDetails();
1127
+ }
1071
1128
  }