@fluidframework/container-runtime 2.118.1 → 3.0.1

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 (326) hide show
  1. package/CHANGELOG.md +68 -4
  2. package/README.md +53 -50
  3. package/{api-extractor.json → api-extractor/api-extractor-model.json} +1 -1
  4. package/api-report/container-runtime.legacy.alpha.api.md +3 -1
  5. package/api-report/container-runtime.legacy.beta.api.md +3 -1
  6. package/container-runtime.test-files.tar +0 -0
  7. package/dist/batchTracker.d.ts.map +1 -1
  8. package/dist/batchTracker.js +5 -1
  9. package/dist/batchTracker.js.map +1 -1
  10. package/dist/blobManager/blobManager.d.ts +1 -1
  11. package/dist/blobManager/blobManager.d.ts.map +1 -1
  12. package/dist/blobManager/blobManager.js +264 -231
  13. package/dist/blobManager/blobManager.js.map +1 -1
  14. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  15. package/dist/channelCollection.d.ts.map +1 -1
  16. package/dist/channelCollection.js +70 -58
  17. package/dist/channelCollection.js.map +1 -1
  18. package/dist/connectionTelemetry.js +38 -20
  19. package/dist/connectionTelemetry.js.map +1 -1
  20. package/dist/containerCompatibility.d.ts +3 -3
  21. package/dist/containerCompatibility.d.ts.map +1 -1
  22. package/dist/containerCompatibility.js +36 -45
  23. package/dist/containerCompatibility.js.map +1 -1
  24. package/dist/containerHandleContext.d.ts.map +1 -1
  25. package/dist/containerHandleContext.js +4 -0
  26. package/dist/containerHandleContext.js.map +1 -1
  27. package/dist/containerRuntime.d.ts +4 -4
  28. package/dist/containerRuntime.d.ts.map +1 -1
  29. package/dist/containerRuntime.js +346 -223
  30. package/dist/containerRuntime.js.map +1 -1
  31. package/dist/dataStore.d.ts.map +1 -1
  32. package/dist/dataStore.js +9 -1
  33. package/dist/dataStore.js.map +1 -1
  34. package/dist/dataStoreContext.d.ts.map +1 -1
  35. package/dist/dataStoreContext.js +184 -127
  36. package/dist/dataStoreContext.js.map +1 -1
  37. package/dist/dataStoreContexts.js +43 -42
  38. package/dist/dataStoreContexts.js.map +1 -1
  39. package/dist/dataStoreRegistry.js +1 -0
  40. package/dist/dataStoreRegistry.js.map +1 -1
  41. package/dist/deltaManagerProxies.js +5 -0
  42. package/dist/deltaManagerProxies.js.map +1 -1
  43. package/dist/deltaScheduler.js +104 -99
  44. package/dist/deltaScheduler.js.map +1 -1
  45. package/dist/error.js +2 -1
  46. package/dist/error.js.map +1 -1
  47. package/dist/gc/garbageCollection.js +85 -55
  48. package/dist/gc/garbageCollection.js.map +1 -1
  49. package/dist/gc/gcConfigs.js +2 -3
  50. package/dist/gc/gcConfigs.js.map +1 -1
  51. package/dist/gc/gcHelpers.js +12 -13
  52. package/dist/gc/gcHelpers.js.map +1 -1
  53. package/dist/gc/gcReferenceGraphAlgorithm.js +1 -2
  54. package/dist/gc/gcReferenceGraphAlgorithm.js.map +1 -1
  55. package/dist/gc/gcSummaryStateTracker.js +8 -3
  56. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  57. package/dist/gc/gcTelemetry.js +12 -5
  58. package/dist/gc/gcTelemetry.js.map +1 -1
  59. package/dist/gc/gcUnreferencedStateTracker.js +18 -1
  60. package/dist/gc/gcUnreferencedStateTracker.js.map +1 -1
  61. package/dist/inboundBatchAggregator.js +122 -117
  62. package/dist/inboundBatchAggregator.js.map +1 -1
  63. package/dist/metadata.d.ts.map +1 -1
  64. package/dist/metadata.js +3 -3
  65. package/dist/metadata.js.map +1 -1
  66. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  67. package/dist/opLifecycle/batchManager.js +11 -5
  68. package/dist/opLifecycle/batchManager.js.map +1 -1
  69. package/dist/opLifecycle/duplicateBatchDetector.js +18 -18
  70. package/dist/opLifecycle/duplicateBatchDetector.js.map +1 -1
  71. package/dist/opLifecycle/index.d.ts +2 -2
  72. package/dist/opLifecycle/index.d.ts.map +1 -1
  73. package/dist/opLifecycle/index.js +2 -1
  74. package/dist/opLifecycle/index.js.map +1 -1
  75. package/dist/opLifecycle/opCompressor.js +2 -1
  76. package/dist/opLifecycle/opCompressor.js.map +1 -1
  77. package/dist/opLifecycle/opDecompressor.js +10 -6
  78. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  79. package/dist/opLifecycle/opGroupingManager.js +5 -3
  80. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  81. package/dist/opLifecycle/opSerialization.d.ts +11 -1
  82. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  83. package/dist/opLifecycle/opSerialization.js +22 -6
  84. package/dist/opLifecycle/opSerialization.js.map +1 -1
  85. package/dist/opLifecycle/opSplitter.d.ts +17 -1
  86. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  87. package/dist/opLifecycle/opSplitter.js +46 -9
  88. package/dist/opLifecycle/opSplitter.js.map +1 -1
  89. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  90. package/dist/opLifecycle/outbox.js +17 -13
  91. package/dist/opLifecycle/outbox.js.map +1 -1
  92. package/dist/opLifecycle/remoteMessageProcessor.js +6 -3
  93. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  94. package/dist/opProperties.d.ts.map +1 -1
  95. package/dist/packageVersion.d.ts +1 -1
  96. package/dist/packageVersion.d.ts.map +1 -1
  97. package/dist/packageVersion.js +1 -1
  98. package/dist/packageVersion.js.map +1 -1
  99. package/dist/pendingStateManager.js +54 -47
  100. package/dist/pendingStateManager.js.map +1 -1
  101. package/dist/runCounter.js +10 -30
  102. package/dist/runCounter.js.map +1 -1
  103. package/dist/runtimeLayerCompatState.d.ts +1 -1
  104. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  105. package/dist/runtimeLayerCompatState.js +31 -11
  106. package/dist/runtimeLayerCompatState.js.map +1 -1
  107. package/dist/signalTelemetryProcessing.js +15 -17
  108. package/dist/signalTelemetryProcessing.js.map +1 -1
  109. package/dist/storageServiceWithAttachBlobs.js +2 -0
  110. package/dist/storageServiceWithAttachBlobs.js.map +1 -1
  111. package/dist/summary/documentSchema.d.ts.map +1 -1
  112. package/dist/summary/documentSchema.js +51 -13
  113. package/dist/summary/documentSchema.js.map +1 -1
  114. package/dist/summary/orderedClientElection.js +25 -17
  115. package/dist/summary/orderedClientElection.js.map +1 -1
  116. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  117. package/dist/summary/summarizerClientElection.js +18 -7
  118. package/dist/summary/summarizerClientElection.js.map +1 -1
  119. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  120. package/dist/summary/summarizerNode/summarizerNode.js +22 -13
  121. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  122. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  123. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +20 -6
  124. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  125. package/dist/summary/summarizerUtils.d.ts.map +1 -1
  126. package/dist/summary/summarizerUtils.js +4 -3
  127. package/dist/summary/summarizerUtils.js.map +1 -1
  128. package/dist/summary/summaryCollection.js +23 -11
  129. package/dist/summary/summaryCollection.js.map +1 -1
  130. package/dist/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js +4 -2
  131. package/dist/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js.map +1 -1
  132. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  133. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js +65 -46
  134. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  135. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  136. package/dist/summary/summaryDelayLoadedModule/summarizer.js +15 -6
  137. package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  138. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js +30 -25
  139. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  140. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js +9 -0
  141. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  142. package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.js +3 -5
  143. package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.js.map +1 -1
  144. package/dist/summary/summaryFormat.d.ts.map +1 -1
  145. package/dist/summary/summaryFormat.js +7 -7
  146. package/dist/summary/summaryFormat.js.map +1 -1
  147. package/dist/summary/summaryHelpers.js +5 -5
  148. package/dist/summary/summaryHelpers.js.map +1 -1
  149. package/dist/summary/summaryManager.js +66 -54
  150. package/dist/summary/summaryManager.js.map +1 -1
  151. package/dist/throttler.d.ts.map +1 -1
  152. package/dist/throttler.js +4 -1
  153. package/dist/throttler.js.map +1 -1
  154. package/dist/versionMarks/inboundBatch.d.ts +8 -6
  155. package/dist/versionMarks/inboundBatch.d.ts.map +1 -1
  156. package/dist/versionMarks/inboundBatch.js +11 -7
  157. package/dist/versionMarks/inboundBatch.js.map +1 -1
  158. package/dist/versionMarks/versionMarkResolver.d.ts +30 -23
  159. package/dist/versionMarks/versionMarkResolver.d.ts.map +1 -1
  160. package/dist/versionMarks/versionMarkResolver.js +112 -63
  161. package/dist/versionMarks/versionMarkResolver.js.map +1 -1
  162. package/lib/batchTracker.d.ts.map +1 -1
  163. package/lib/batchTracker.js +5 -1
  164. package/lib/batchTracker.js.map +1 -1
  165. package/lib/blobManager/blobManager.d.ts +1 -1
  166. package/lib/blobManager/blobManager.d.ts.map +1 -1
  167. package/lib/blobManager/blobManager.js +264 -231
  168. package/lib/blobManager/blobManager.js.map +1 -1
  169. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  170. package/lib/channelCollection.d.ts.map +1 -1
  171. package/lib/channelCollection.js +65 -53
  172. package/lib/channelCollection.js.map +1 -1
  173. package/lib/connectionTelemetry.js +36 -18
  174. package/lib/connectionTelemetry.js.map +1 -1
  175. package/lib/containerCompatibility.d.ts +3 -3
  176. package/lib/containerCompatibility.d.ts.map +1 -1
  177. package/lib/containerCompatibility.js +33 -42
  178. package/lib/containerCompatibility.js.map +1 -1
  179. package/lib/containerHandleContext.d.ts.map +1 -1
  180. package/lib/containerHandleContext.js +4 -0
  181. package/lib/containerHandleContext.js.map +1 -1
  182. package/lib/containerRuntime.d.ts +4 -4
  183. package/lib/containerRuntime.d.ts.map +1 -1
  184. package/lib/containerRuntime.js +341 -218
  185. package/lib/containerRuntime.js.map +1 -1
  186. package/lib/dataStore.d.ts.map +1 -1
  187. package/lib/dataStore.js +9 -1
  188. package/lib/dataStore.js.map +1 -1
  189. package/lib/dataStoreContext.d.ts.map +1 -1
  190. package/lib/dataStoreContext.js +182 -125
  191. package/lib/dataStoreContext.js.map +1 -1
  192. package/lib/dataStoreContexts.js +43 -42
  193. package/lib/dataStoreContexts.js.map +1 -1
  194. package/lib/dataStoreRegistry.js +1 -0
  195. package/lib/dataStoreRegistry.js.map +1 -1
  196. package/lib/deltaManagerProxies.js +5 -0
  197. package/lib/deltaManagerProxies.js.map +1 -1
  198. package/lib/deltaScheduler.js +104 -99
  199. package/lib/deltaScheduler.js.map +1 -1
  200. package/lib/error.js +2 -1
  201. package/lib/error.js.map +1 -1
  202. package/lib/gc/garbageCollection.js +85 -55
  203. package/lib/gc/garbageCollection.js.map +1 -1
  204. package/lib/gc/gcSummaryStateTracker.js +8 -3
  205. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  206. package/lib/gc/gcTelemetry.js +12 -5
  207. package/lib/gc/gcTelemetry.js.map +1 -1
  208. package/lib/gc/gcUnreferencedStateTracker.js +18 -1
  209. package/lib/gc/gcUnreferencedStateTracker.js.map +1 -1
  210. package/lib/inboundBatchAggregator.js +122 -117
  211. package/lib/inboundBatchAggregator.js.map +1 -1
  212. package/lib/legacy.d.ts +1 -1
  213. package/lib/legacyAlpha.d.ts +1 -1
  214. package/lib/metadata.d.ts.map +1 -1
  215. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  216. package/lib/opLifecycle/batchManager.js +8 -2
  217. package/lib/opLifecycle/batchManager.js.map +1 -1
  218. package/lib/opLifecycle/duplicateBatchDetector.js +18 -18
  219. package/lib/opLifecycle/duplicateBatchDetector.js.map +1 -1
  220. package/lib/opLifecycle/index.d.ts +2 -2
  221. package/lib/opLifecycle/index.d.ts.map +1 -1
  222. package/lib/opLifecycle/index.js +2 -2
  223. package/lib/opLifecycle/index.js.map +1 -1
  224. package/lib/opLifecycle/opCompressor.js +3 -2
  225. package/lib/opLifecycle/opCompressor.js.map +1 -1
  226. package/lib/opLifecycle/opDecompressor.js +10 -6
  227. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  228. package/lib/opLifecycle/opGroupingManager.js +3 -1
  229. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  230. package/lib/opLifecycle/opSerialization.d.ts +11 -1
  231. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  232. package/lib/opLifecycle/opSerialization.js +16 -0
  233. package/lib/opLifecycle/opSerialization.js.map +1 -1
  234. package/lib/opLifecycle/opSplitter.d.ts +17 -1
  235. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  236. package/lib/opLifecycle/opSplitter.js +44 -7
  237. package/lib/opLifecycle/opSplitter.js.map +1 -1
  238. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  239. package/lib/opLifecycle/outbox.js +14 -10
  240. package/lib/opLifecycle/outbox.js.map +1 -1
  241. package/lib/opLifecycle/remoteMessageProcessor.js +4 -1
  242. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  243. package/lib/opProperties.d.ts.map +1 -1
  244. package/lib/packageVersion.d.ts +1 -1
  245. package/lib/packageVersion.d.ts.map +1 -1
  246. package/lib/packageVersion.js +1 -1
  247. package/lib/packageVersion.js.map +1 -1
  248. package/lib/pendingStateManager.js +52 -45
  249. package/lib/pendingStateManager.js.map +1 -1
  250. package/lib/public.d.ts +1 -1
  251. package/lib/runCounter.js +10 -30
  252. package/lib/runCounter.js.map +1 -1
  253. package/lib/runtimeLayerCompatState.d.ts +1 -1
  254. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  255. package/lib/runtimeLayerCompatState.js +28 -8
  256. package/lib/runtimeLayerCompatState.js.map +1 -1
  257. package/lib/signalTelemetryProcessing.js +15 -17
  258. package/lib/signalTelemetryProcessing.js.map +1 -1
  259. package/lib/storageServiceWithAttachBlobs.js +2 -0
  260. package/lib/storageServiceWithAttachBlobs.js.map +1 -1
  261. package/lib/summary/documentSchema.d.ts.map +1 -1
  262. package/lib/summary/documentSchema.js +51 -13
  263. package/lib/summary/documentSchema.js.map +1 -1
  264. package/lib/summary/orderedClientElection.js +25 -17
  265. package/lib/summary/orderedClientElection.js.map +1 -1
  266. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  267. package/lib/summary/summarizerClientElection.js +18 -7
  268. package/lib/summary/summarizerClientElection.js.map +1 -1
  269. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  270. package/lib/summary/summarizerNode/summarizerNode.js +22 -13
  271. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  272. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  273. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +20 -6
  274. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  275. package/lib/summary/summarizerUtils.d.ts.map +1 -1
  276. package/lib/summary/summarizerUtils.js +1 -0
  277. package/lib/summary/summarizerUtils.js.map +1 -1
  278. package/lib/summary/summaryCollection.js +23 -11
  279. package/lib/summary/summaryCollection.js.map +1 -1
  280. package/lib/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js +4 -2
  281. package/lib/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js.map +1 -1
  282. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  283. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js +65 -46
  284. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  285. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  286. package/lib/summary/summaryDelayLoadedModule/summarizer.js +15 -6
  287. package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  288. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js +30 -25
  289. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  290. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js +9 -0
  291. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  292. package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.js +3 -5
  293. package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.js.map +1 -1
  294. package/lib/summary/summaryFormat.d.ts.map +1 -1
  295. package/lib/summary/summaryManager.js +66 -54
  296. package/lib/summary/summaryManager.js.map +1 -1
  297. package/lib/throttler.d.ts.map +1 -1
  298. package/lib/throttler.js +4 -1
  299. package/lib/throttler.js.map +1 -1
  300. package/lib/versionMarks/inboundBatch.d.ts +8 -6
  301. package/lib/versionMarks/inboundBatch.d.ts.map +1 -1
  302. package/lib/versionMarks/inboundBatch.js +10 -5
  303. package/lib/versionMarks/inboundBatch.js.map +1 -1
  304. package/lib/versionMarks/versionMarkResolver.d.ts +30 -23
  305. package/lib/versionMarks/versionMarkResolver.d.ts.map +1 -1
  306. package/lib/versionMarks/versionMarkResolver.js +112 -63
  307. package/lib/versionMarks/versionMarkResolver.js.map +1 -1
  308. package/package.json +60 -102
  309. package/src/containerCompatibility.ts +33 -42
  310. package/src/containerRuntime.ts +15 -12
  311. package/src/opLifecycle/index.ts +7 -1
  312. package/src/opLifecycle/opCompressor.ts +2 -2
  313. package/src/opLifecycle/opSerialization.ts +22 -1
  314. package/src/opLifecycle/opSplitter.ts +63 -6
  315. package/src/packageVersion.ts +1 -1
  316. package/src/runtimeLayerCompatState.ts +28 -21
  317. package/src/summary/documentSchema.ts +31 -6
  318. package/src/versionMarks/DEV.md +595 -157
  319. package/src/versionMarks/inboundBatch.ts +18 -8
  320. package/src/versionMarks/versionMarkResolver.ts +144 -75
  321. package/tsconfig.json +1 -1
  322. package/internal.d.ts +0 -11
  323. package/legacy/alpha.d.ts +0 -11
  324. package/legacy.d.ts +0 -11
  325. /package/api-extractor/{api-extractor.current.json → api-extractor-report.current.json} +0 -0
  326. /package/api-extractor/{api-extractor.legacy.json → api-extractor-report.legacy.json} +0 -0
@@ -18,18 +18,34 @@ import { summarizeBlobManagerState, toRedirectTable, } from "./blobManagerSnapSu
18
18
  * and loads blob.
19
19
  */
20
20
  export class BlobHandle extends FluidHandleBase {
21
+ path;
22
+ routeContext;
23
+ get;
24
+ payloadPending;
25
+ sharePayloadCallback;
26
+ attachGraphCallback;
27
+ attached = false;
28
+ payloadSharingStarted = false;
21
29
  get isAttached() {
22
30
  return this.routeContext.isAttached && this.attached;
23
31
  }
32
+ _events;
24
33
  get events() {
25
34
  return (this._events ??= createEmitter());
26
35
  }
36
+ _payloadState;
27
37
  get payloadState() {
28
38
  return this._payloadState;
29
39
  }
40
+ /**
41
+ * The error property starts undefined, signalling that there has been no error yet.
42
+ * If an error occurs, the property will contain the error.
43
+ */
44
+ _payloadShareError;
30
45
  get payloadShareError() {
31
46
  return this._payloadShareError;
32
47
  }
48
+ absolutePath;
33
49
  constructor(path, routeContext,
34
50
  // TODO: just take the blob rather than a get function?
35
51
  get, payloadPending, sharePayloadCallback, attachGraphCallback) {
@@ -40,19 +56,17 @@ export class BlobHandle extends FluidHandleBase {
40
56
  this.payloadPending = payloadPending;
41
57
  this.sharePayloadCallback = sharePayloadCallback;
42
58
  this.attachGraphCallback = attachGraphCallback;
43
- this.attached = false;
44
- this.payloadSharingStarted = false;
45
- this.notifyShared = () => {
46
- this._payloadState = "shared";
47
- this._events?.emit("payloadShared");
48
- };
49
- this.notifyFailed = (error) => {
50
- this._payloadShareError = error;
51
- this._events?.emit("payloadShareFailed", error);
52
- };
53
59
  this._payloadState = payloadPending ? "pending" : "shared";
54
60
  this.absolutePath = generateHandleContextPath(path, this.routeContext);
55
61
  }
62
+ notifyShared = () => {
63
+ this._payloadState = "shared";
64
+ this._events?.emit("payloadShared");
65
+ };
66
+ notifyFailed = (error) => {
67
+ this._payloadShareError = error;
68
+ this._events?.emit("payloadShareFailed", error);
69
+ };
56
70
  sharePayload() {
57
71
  if (this.payloadState === "shared" || this.payloadSharingStarted) {
58
72
  return;
@@ -80,228 +94,41 @@ const createAbortError = () => new LoggingError("uploadBlob aborted");
80
94
  */
81
95
  export const blobManagerBasePath = "_blobs";
82
96
  export class BlobManager {
97
+ mc;
98
+ internalEvents = createEmitter();
99
+ /**
100
+ * Map of local IDs to storage IDs. Also includes identity mappings of storage ID to storage ID for all known
101
+ * storage IDs. All requested IDs must be a key in this map. Blobs created while the container is detached are
102
+ * stored in IDetachedBlobStorage which gives pseudo storage IDs; the real storage IDs are filled in at attach
103
+ * time via setRedirectTable().
104
+ */
105
+ redirectTable;
106
+ /**
107
+ * The localBlobCache has a dual role of caching locally-created blobs, as well as tracking their state as they
108
+ * are shared. Keys are localIds.
109
+ */
110
+ localBlobCache = new Map();
111
+ /**
112
+ * Blobs with an attached handle that have not finished blob-attaching are the set we need to provide from
113
+ * getPendingState(). This stores their local IDs, and then we can look them up against the localBlobCache.
114
+ */
115
+ pendingBlobsWithAttachedHandles = new Set();
116
+ /**
117
+ * Local IDs for any pending blobs we loaded with and have not yet started the upload/attach flow for.
118
+ */
119
+ pendingOnlyLocalIds = new Set();
120
+ sendBlobAttachMessage;
121
+ routeContext;
122
+ storage;
123
+ // Called when a blob node is requested. blobPath is the path of the blob's node in GC's graph.
124
+ // blobPath's format - `/<basePath>/<localId>`.
125
+ blobRequested;
126
+ // Called to check if a blob has been deleted by GC.
127
+ // blobPath's format - `/<basePath>/<localId>`.
128
+ isBlobDeleted;
129
+ runtime;
130
+ createBlobPayloadPending;
83
131
  constructor(props) {
84
- this.internalEvents = createEmitter();
85
- /**
86
- * The localBlobCache has a dual role of caching locally-created blobs, as well as tracking their state as they
87
- * are shared. Keys are localIds.
88
- */
89
- this.localBlobCache = new Map();
90
- /**
91
- * Blobs with an attached handle that have not finished blob-attaching are the set we need to provide from
92
- * getPendingState(). This stores their local IDs, and then we can look them up against the localBlobCache.
93
- */
94
- this.pendingBlobsWithAttachedHandles = new Set();
95
- /**
96
- * Local IDs for any pending blobs we loaded with and have not yet started the upload/attach flow for.
97
- */
98
- this.pendingOnlyLocalIds = new Set();
99
- /**
100
- * Upload and attach the localBlobCache entry for the given localId.
101
- *
102
- * Expects the localBlobCache entry for the given localId to be in either localOnly or uploaded state
103
- * when called. Returns a promise that resolves when the blob completes uploading and attaching, or else
104
- * rejects if an error is encountered or the signal is aborted.
105
- */
106
- this.uploadAndAttach = async (localId, signal) => {
107
- if (signal?.aborted === true) {
108
- this.localBlobCache.delete(localId);
109
- this.pendingBlobsWithAttachedHandles.delete(localId);
110
- throw createAbortError();
111
- }
112
- const localBlobRecordInitial = this.localBlobCache.get(localId);
113
- assert(localBlobRecordInitial?.state === "localOnly" ||
114
- localBlobRecordInitial?.state === "uploaded", 0xc80 /* Expect uploadAndAttach to be called with either localOnly or uploaded state */);
115
- const { blob } = localBlobRecordInitial;
116
- /**
117
- * Expects the localBlobCache entry for the given localId to be in either localOnly or uploaded state
118
- * when called. Returns a promise that resolves when the blob is in uploaded or attached state, or else
119
- * rejects on error during upload or if the signal is aborted.
120
- *
121
- * Most of the time this should be expected to exit in uploaded state, but if we are loading from pending
122
- * state we may see an attach message from the client that generated the pending state, which can complete the
123
- * attach while the upload is outstanding.
124
- */
125
- const ensureUploaded = async () => {
126
- const localBlobRecord = this.localBlobCache.get(localId);
127
- if (localBlobRecord?.state === "uploaded") {
128
- // In normal creation flows, the blob will be in localOnly state here. But in the case of loading
129
- // with pending state we can call it with an uploaded-but-not-attached blob. Start the upload
130
- // flow only if it's localOnly.
131
- return;
132
- }
133
- assert(localBlobRecord?.state === "localOnly", 0xc81 /* Attempting to upload from unexpected state */);
134
- this.localBlobCache.set(localId, { state: "uploading", blob });
135
- await new Promise((resolve, reject) => {
136
- // If we eventually have driver-level support for abort, then this can simplify a bit as we won't
137
- // need to track upload completion and abort separately. Until then, we need to handle the case that
138
- // the upload continues and settles after becoming irrelevant due to signal abort or blob attach.
139
- let uploadHasBecomeIrrelevant = false;
140
- const onSignalAbort = () => {
141
- removeListeners();
142
- uploadHasBecomeIrrelevant = true;
143
- this.localBlobCache.delete(localId);
144
- this.pendingBlobsWithAttachedHandles.delete(localId);
145
- reject(createAbortError());
146
- };
147
- const onProcessedBlobAttach = (_localId, _storageId) => {
148
- if (_localId === localId) {
149
- removeListeners();
150
- uploadHasBecomeIrrelevant = true;
151
- resolve();
152
- }
153
- };
154
- const removeListeners = () => {
155
- this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
156
- signal?.removeEventListener("abort", onSignalAbort);
157
- };
158
- this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
159
- signal?.addEventListener("abort", onSignalAbort);
160
- this.storage
161
- .createBlob(blob)
162
- .then((createBlobResponse) => {
163
- if (!uploadHasBecomeIrrelevant) {
164
- removeListeners();
165
- this.localBlobCache.set(localId, {
166
- state: "uploaded",
167
- blob,
168
- storageId: createBlobResponse.id,
169
- uploadTime: Date.now(),
170
- minTTLInSeconds: createBlobResponse.minTTLInSeconds,
171
- });
172
- resolve();
173
- }
174
- })
175
- .catch((error) => {
176
- if (!uploadHasBecomeIrrelevant) {
177
- removeListeners();
178
- // If the storage call errors, we can't recover. Reject to throw back to the caller.
179
- this.localBlobCache.delete(localId);
180
- this.pendingBlobsWithAttachedHandles.delete(localId);
181
- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
182
- reject(error);
183
- }
184
- });
185
- });
186
- };
187
- /**
188
- * Expects the localBlobCache entry for the given localId to be in uploaded or attached state when called.
189
- * Returns a promise that resolves to true if the blob is successfully attached, or false if it cannot be
190
- * attached and the upload flow needs to be restarted from the top (currently only if the TTL expires before
191
- * attach can be completed). In the latter case, the localBlobRecord will also be reset to localOnly state.
192
- * The promise rejects if the signal is aborted.
193
- */
194
- const tryAttach = async () => {
195
- const localBlobRecord = this.localBlobCache.get(localId);
196
- if (localBlobRecord?.state === "attached") {
197
- // In normal creation flows, the blob will be in uploaded state here. But if we are loading from pending
198
- // state and see an attach message from the client that generated the pending state, we may have reached
199
- // attached state in the middle of the upload attempt. In that case there's no more work to do and we
200
- // can just return.
201
- return true;
202
- }
203
- assert(localBlobRecord?.state === "uploaded", 0xc82 /* Attempting to attach from unexpected state */);
204
- // If we just uploaded the blob TTL really shouldn't be expired at this location. But if we loaded from
205
- // pending state, the upload may have happened some time far in the past and could be expired here.
206
- if (isTTLTooCloseToExpiry(localBlobRecord)) {
207
- // If the TTL is expired, we assume it's gone from the storage and so is effectively localOnly again.
208
- // Then when we re-enter the loop, we'll re-upload it.
209
- this.localBlobCache.set(localId, { state: "localOnly", blob });
210
- // Emitting here isn't really necessary since the only listener would be attached below. Including here
211
- // for completeness though, in case we add other listeners in the future.
212
- this.internalEvents.emit("blobExpired", localId);
213
- return false;
214
- }
215
- else {
216
- this.localBlobCache.set(localId, {
217
- ...localBlobRecord,
218
- state: "attaching",
219
- });
220
- // Send and await a blob attach op. This serves two purposes:
221
- // 1. If its a new blob, i.e., it isn't de-duped, the server will keep the blob alive if it sees this op
222
- // until its storage ID is added to the next summary.
223
- // 2. It will create a local ID to storage ID mapping in all clients which is needed to retrieve the
224
- // blob from the server via the storage ID.
225
- return new Promise((resolve, reject) => {
226
- const onProcessedBlobAttach = (_localId, _storageId) => {
227
- if (_localId === localId) {
228
- removeListeners();
229
- resolve(true);
230
- }
231
- };
232
- // Although we already checked for TTL expiry above, the message we're about to send may later be asked
233
- // to resubmit. Before we resubmit, we check again for TTL expiry - this listener is how we learn if
234
- // we discovered expiry in the resubmit flow.
235
- const onBlobExpired = (_localId) => {
236
- if (_localId === localId) {
237
- removeListeners();
238
- resolve(false);
239
- }
240
- };
241
- const onSignalAbort = () => {
242
- removeListeners();
243
- this.localBlobCache.delete(localId);
244
- this.pendingBlobsWithAttachedHandles.delete(localId);
245
- reject(createAbortError());
246
- };
247
- const removeListeners = () => {
248
- this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
249
- this.internalEvents.off("blobExpired", onBlobExpired);
250
- signal?.removeEventListener("abort", onSignalAbort);
251
- };
252
- this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
253
- this.internalEvents.on("blobExpired", onBlobExpired);
254
- signal?.addEventListener("abort", onSignalAbort);
255
- this.sendBlobAttachMessage(localId, localBlobRecord.storageId);
256
- });
257
- }
258
- };
259
- let attachCompleted = false;
260
- while (!attachCompleted) {
261
- await ensureUploaded();
262
- attachCompleted = await tryAttach();
263
- // If something stopped the attach from completing successfully (currently just TTL expiry),
264
- // we expect that the blob was already updated to reflect the updated state (i.e. back to localOnly)
265
- // and we'll try the loop again from the top.
266
- }
267
- // When the blob successfully attaches, the localBlobRecord will have been updated to attached state
268
- // at the time we processed the op, so there's nothing else to do here.
269
- };
270
- /**
271
- * Called in detached state just prior to attaching, this will update the redirect table by
272
- * converting the pseudo storage IDs into real storage IDs using the provided detachedStorageTable.
273
- * The provided table must have exactly the same set of pseudo storage IDs as are found in the redirect table.
274
- * @param detachedStorageTable - A map of pseudo storage IDs to real storage IDs.
275
- */
276
- this.patchRedirectTable = (detachedStorageTable) => {
277
- assert(this.runtime.attachState === AttachState.Detached, 0x252 /* "redirect table can only be set in detached container" */);
278
- // The values of the redirect table are the pseudo storage IDs, which are the keys of the
279
- // detachedStorageTable. We expect to have a many:1 mapping from local IDs to pseudo
280
- // storage IDs (many in the case that the storage dedupes the blob).
281
- assert(new Set(this.redirectTable.values()).size === detachedStorageTable.size, 0x391 /* Redirect table size must match BlobManager's local ID count */);
282
- // Taking a snapshot of the redirect table entries before iterating, because
283
- // we will be adding identity mappings to the the redirect table as we iterate
284
- // and we don't want to include those in the iteration.
285
- const redirectTableEntries = [...this.redirectTable.entries()];
286
- for (const [localId, detachedStorageId] of redirectTableEntries) {
287
- const newStorageId = detachedStorageTable.get(detachedStorageId);
288
- assert(newStorageId !== undefined, 0xc53 /* Couldn't find a matching storage ID */);
289
- this.redirectTable.set(localId, newStorageId);
290
- // set identity (id -> id) entry
291
- this.redirectTable.set(newStorageId, newStorageId);
292
- }
293
- };
294
- /**
295
- * Upload and attach any pending blobs that the BlobManager was loaded with that have not already
296
- * been attached in the meantime.
297
- * @returns A promise that resolves when all the uploads and attaches have completed, or rejects
298
- * if any of them fail.
299
- */
300
- this.sharePendingBlobs = async () => {
301
- const localIdsToUpload = [...this.pendingOnlyLocalIds];
302
- this.pendingOnlyLocalIds.clear();
303
- await Promise.all(localIdsToUpload.map(async (localId) => this.uploadAndAttach(localId)));
304
- };
305
132
  const { routeContext, blobManagerLoadInfo, storage, sendBlobAttachMessage, blobRequested, isBlobDeleted, runtime, pendingBlobs, createBlobPayloadPending, } = props;
306
133
  this.routeContext = routeContext;
307
134
  this.storage = storage;
@@ -469,6 +296,177 @@ export class BlobManager {
469
296
  });
470
297
  return blobHandle;
471
298
  }
299
+ /**
300
+ * Upload and attach the localBlobCache entry for the given localId.
301
+ *
302
+ * Expects the localBlobCache entry for the given localId to be in either localOnly or uploaded state
303
+ * when called. Returns a promise that resolves when the blob completes uploading and attaching, or else
304
+ * rejects if an error is encountered or the signal is aborted.
305
+ */
306
+ uploadAndAttach = async (localId, signal) => {
307
+ if (signal?.aborted === true) {
308
+ this.localBlobCache.delete(localId);
309
+ this.pendingBlobsWithAttachedHandles.delete(localId);
310
+ throw createAbortError();
311
+ }
312
+ const localBlobRecordInitial = this.localBlobCache.get(localId);
313
+ assert(localBlobRecordInitial?.state === "localOnly" ||
314
+ localBlobRecordInitial?.state === "uploaded", 0xc80 /* Expect uploadAndAttach to be called with either localOnly or uploaded state */);
315
+ const { blob } = localBlobRecordInitial;
316
+ /**
317
+ * Expects the localBlobCache entry for the given localId to be in either localOnly or uploaded state
318
+ * when called. Returns a promise that resolves when the blob is in uploaded or attached state, or else
319
+ * rejects on error during upload or if the signal is aborted.
320
+ *
321
+ * Most of the time this should be expected to exit in uploaded state, but if we are loading from pending
322
+ * state we may see an attach message from the client that generated the pending state, which can complete the
323
+ * attach while the upload is outstanding.
324
+ */
325
+ const ensureUploaded = async () => {
326
+ const localBlobRecord = this.localBlobCache.get(localId);
327
+ if (localBlobRecord?.state === "uploaded") {
328
+ // In normal creation flows, the blob will be in localOnly state here. But in the case of loading
329
+ // with pending state we can call it with an uploaded-but-not-attached blob. Start the upload
330
+ // flow only if it's localOnly.
331
+ return;
332
+ }
333
+ assert(localBlobRecord?.state === "localOnly", 0xc81 /* Attempting to upload from unexpected state */);
334
+ this.localBlobCache.set(localId, { state: "uploading", blob });
335
+ await new Promise((resolve, reject) => {
336
+ // If we eventually have driver-level support for abort, then this can simplify a bit as we won't
337
+ // need to track upload completion and abort separately. Until then, we need to handle the case that
338
+ // the upload continues and settles after becoming irrelevant due to signal abort or blob attach.
339
+ let uploadHasBecomeIrrelevant = false;
340
+ const onSignalAbort = () => {
341
+ removeListeners();
342
+ uploadHasBecomeIrrelevant = true;
343
+ this.localBlobCache.delete(localId);
344
+ this.pendingBlobsWithAttachedHandles.delete(localId);
345
+ reject(createAbortError());
346
+ };
347
+ const onProcessedBlobAttach = (_localId, _storageId) => {
348
+ if (_localId === localId) {
349
+ removeListeners();
350
+ uploadHasBecomeIrrelevant = true;
351
+ resolve();
352
+ }
353
+ };
354
+ const removeListeners = () => {
355
+ this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
356
+ signal?.removeEventListener("abort", onSignalAbort);
357
+ };
358
+ this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
359
+ signal?.addEventListener("abort", onSignalAbort);
360
+ this.storage
361
+ .createBlob(blob)
362
+ .then((createBlobResponse) => {
363
+ if (!uploadHasBecomeIrrelevant) {
364
+ removeListeners();
365
+ this.localBlobCache.set(localId, {
366
+ state: "uploaded",
367
+ blob,
368
+ storageId: createBlobResponse.id,
369
+ uploadTime: Date.now(),
370
+ minTTLInSeconds: createBlobResponse.minTTLInSeconds,
371
+ });
372
+ resolve();
373
+ }
374
+ })
375
+ .catch((error) => {
376
+ if (!uploadHasBecomeIrrelevant) {
377
+ removeListeners();
378
+ // If the storage call errors, we can't recover. Reject to throw back to the caller.
379
+ this.localBlobCache.delete(localId);
380
+ this.pendingBlobsWithAttachedHandles.delete(localId);
381
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
382
+ reject(error);
383
+ }
384
+ });
385
+ });
386
+ };
387
+ /**
388
+ * Expects the localBlobCache entry for the given localId to be in uploaded or attached state when called.
389
+ * Returns a promise that resolves to true if the blob is successfully attached, or false if it cannot be
390
+ * attached and the upload flow needs to be restarted from the top (currently only if the TTL expires before
391
+ * attach can be completed). In the latter case, the localBlobRecord will also be reset to localOnly state.
392
+ * The promise rejects if the signal is aborted.
393
+ */
394
+ const tryAttach = async () => {
395
+ const localBlobRecord = this.localBlobCache.get(localId);
396
+ if (localBlobRecord?.state === "attached") {
397
+ // In normal creation flows, the blob will be in uploaded state here. But if we are loading from pending
398
+ // state and see an attach message from the client that generated the pending state, we may have reached
399
+ // attached state in the middle of the upload attempt. In that case there's no more work to do and we
400
+ // can just return.
401
+ return true;
402
+ }
403
+ assert(localBlobRecord?.state === "uploaded", 0xc82 /* Attempting to attach from unexpected state */);
404
+ // If we just uploaded the blob TTL really shouldn't be expired at this location. But if we loaded from
405
+ // pending state, the upload may have happened some time far in the past and could be expired here.
406
+ if (isTTLTooCloseToExpiry(localBlobRecord)) {
407
+ // If the TTL is expired, we assume it's gone from the storage and so is effectively localOnly again.
408
+ // Then when we re-enter the loop, we'll re-upload it.
409
+ this.localBlobCache.set(localId, { state: "localOnly", blob });
410
+ // Emitting here isn't really necessary since the only listener would be attached below. Including here
411
+ // for completeness though, in case we add other listeners in the future.
412
+ this.internalEvents.emit("blobExpired", localId);
413
+ return false;
414
+ }
415
+ else {
416
+ this.localBlobCache.set(localId, {
417
+ ...localBlobRecord,
418
+ state: "attaching",
419
+ });
420
+ // Send and await a blob attach op. This serves two purposes:
421
+ // 1. If its a new blob, i.e., it isn't de-duped, the server will keep the blob alive if it sees this op
422
+ // until its storage ID is added to the next summary.
423
+ // 2. It will create a local ID to storage ID mapping in all clients which is needed to retrieve the
424
+ // blob from the server via the storage ID.
425
+ return new Promise((resolve, reject) => {
426
+ const onProcessedBlobAttach = (_localId, _storageId) => {
427
+ if (_localId === localId) {
428
+ removeListeners();
429
+ resolve(true);
430
+ }
431
+ };
432
+ // Although we already checked for TTL expiry above, the message we're about to send may later be asked
433
+ // to resubmit. Before we resubmit, we check again for TTL expiry - this listener is how we learn if
434
+ // we discovered expiry in the resubmit flow.
435
+ const onBlobExpired = (_localId) => {
436
+ if (_localId === localId) {
437
+ removeListeners();
438
+ resolve(false);
439
+ }
440
+ };
441
+ const onSignalAbort = () => {
442
+ removeListeners();
443
+ this.localBlobCache.delete(localId);
444
+ this.pendingBlobsWithAttachedHandles.delete(localId);
445
+ reject(createAbortError());
446
+ };
447
+ const removeListeners = () => {
448
+ this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
449
+ this.internalEvents.off("blobExpired", onBlobExpired);
450
+ signal?.removeEventListener("abort", onSignalAbort);
451
+ };
452
+ this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
453
+ this.internalEvents.on("blobExpired", onBlobExpired);
454
+ signal?.addEventListener("abort", onSignalAbort);
455
+ this.sendBlobAttachMessage(localId, localBlobRecord.storageId);
456
+ });
457
+ }
458
+ };
459
+ let attachCompleted = false;
460
+ while (!attachCompleted) {
461
+ await ensureUploaded();
462
+ attachCompleted = await tryAttach();
463
+ // If something stopped the attach from completing successfully (currently just TTL expiry),
464
+ // we expect that the blob was already updated to reflect the updated state (i.e. back to localOnly)
465
+ // and we'll try the loop again from the top.
466
+ }
467
+ // When the blob successfully attaches, the localBlobRecord will have been updated to attached state
468
+ // at the time we processed the op, so there's nothing else to do here.
469
+ };
472
470
  /**
473
471
  * Resubmit a BlobAttach op. Used to add storage IDs to messages that were
474
472
  * submitted to runtime while disconnected.
@@ -616,6 +614,41 @@ export class BlobManager {
616
614
  }, error);
617
615
  throw error;
618
616
  }
617
+ /**
618
+ * Called in detached state just prior to attaching, this will update the redirect table by
619
+ * converting the pseudo storage IDs into real storage IDs using the provided detachedStorageTable.
620
+ * The provided table must have exactly the same set of pseudo storage IDs as are found in the redirect table.
621
+ * @param detachedStorageTable - A map of pseudo storage IDs to real storage IDs.
622
+ */
623
+ patchRedirectTable = (detachedStorageTable) => {
624
+ assert(this.runtime.attachState === AttachState.Detached, 0x252 /* "redirect table can only be set in detached container" */);
625
+ // The values of the redirect table are the pseudo storage IDs, which are the keys of the
626
+ // detachedStorageTable. We expect to have a many:1 mapping from local IDs to pseudo
627
+ // storage IDs (many in the case that the storage dedupes the blob).
628
+ assert(new Set(this.redirectTable.values()).size === detachedStorageTable.size, 0x391 /* Redirect table size must match BlobManager's local ID count */);
629
+ // Taking a snapshot of the redirect table entries before iterating, because
630
+ // we will be adding identity mappings to the the redirect table as we iterate
631
+ // and we don't want to include those in the iteration.
632
+ const redirectTableEntries = [...this.redirectTable.entries()];
633
+ for (const [localId, detachedStorageId] of redirectTableEntries) {
634
+ const newStorageId = detachedStorageTable.get(detachedStorageId);
635
+ assert(newStorageId !== undefined, 0xc53 /* Couldn't find a matching storage ID */);
636
+ this.redirectTable.set(localId, newStorageId);
637
+ // set identity (id -> id) entry
638
+ this.redirectTable.set(newStorageId, newStorageId);
639
+ }
640
+ };
641
+ /**
642
+ * Upload and attach any pending blobs that the BlobManager was loaded with that have not already
643
+ * been attached in the meantime.
644
+ * @returns A promise that resolves when all the uploads and attaches have completed, or rejects
645
+ * if any of them fail.
646
+ */
647
+ sharePendingBlobs = async () => {
648
+ const localIdsToUpload = [...this.pendingOnlyLocalIds];
649
+ this.pendingOnlyLocalIds.clear();
650
+ await Promise.all(localIdsToUpload.map(async (localId) => this.uploadAndAttach(localId)));
651
+ };
619
652
  /**
620
653
  * To be used in getPendingLocalState flow. Get a serializable record of the blobs that are
621
654
  * pending upload and/or their BlobAttach op, which can be given to a new BlobManager to