@fluidframework/container-runtime 2.0.0-dev-rc.1.0.0.225277 → 2.0.0-dev-rc.1.0.0.232845

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 (299) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +0 -6
  3. package/api-report/container-runtime.api.md +14 -3
  4. package/dist/{batchTracker.cjs → batchTracker.js} +1 -1
  5. package/dist/batchTracker.js.map +1 -0
  6. package/dist/blobManager.d.ts.map +1 -1
  7. package/dist/{blobManager.cjs → blobManager.js} +9 -3
  8. package/dist/blobManager.js.map +1 -0
  9. package/dist/{connectionTelemetry.cjs → connectionTelemetry.js} +1 -1
  10. package/dist/connectionTelemetry.js.map +1 -0
  11. package/dist/container-runtime-alpha.d.ts +13 -3
  12. package/dist/container-runtime-beta.d.ts +7 -0
  13. package/dist/container-runtime-public.d.ts +7 -0
  14. package/dist/container-runtime-untrimmed.d.ts +30 -3
  15. package/dist/{containerHandleContext.cjs → containerHandleContext.js} +1 -1
  16. package/dist/containerHandleContext.js.map +1 -0
  17. package/dist/containerRuntime.d.ts +5 -2
  18. package/dist/containerRuntime.d.ts.map +1 -1
  19. package/dist/{containerRuntime.cjs → containerRuntime.js} +78 -85
  20. package/dist/containerRuntime.js.map +1 -0
  21. package/dist/{dataStore.cjs → dataStore.js} +1 -1
  22. package/dist/dataStore.js.map +1 -0
  23. package/dist/dataStoreContext.d.ts +7 -1
  24. package/dist/dataStoreContext.d.ts.map +1 -1
  25. package/dist/{dataStoreContext.cjs → dataStoreContext.js} +13 -7
  26. package/dist/dataStoreContext.js.map +1 -0
  27. package/dist/{dataStoreContexts.cjs → dataStoreContexts.js} +1 -1
  28. package/dist/dataStoreContexts.js.map +1 -0
  29. package/dist/{dataStoreRegistry.cjs → dataStoreRegistry.js} +1 -1
  30. package/dist/dataStoreRegistry.js.map +1 -0
  31. package/dist/dataStores.d.ts +9 -3
  32. package/dist/dataStores.d.ts.map +1 -1
  33. package/dist/{dataStores.cjs → dataStores.js} +62 -16
  34. package/dist/dataStores.js.map +1 -0
  35. package/dist/{deltaManagerProxyBase.cjs → deltaManagerProxyBase.js} +1 -1
  36. package/dist/deltaManagerProxyBase.js.map +1 -0
  37. package/dist/{deltaManagerSummarizerProxy.cjs → deltaManagerSummarizerProxy.js} +3 -3
  38. package/dist/deltaManagerSummarizerProxy.js.map +1 -0
  39. package/dist/{deltaScheduler.cjs → deltaScheduler.js} +1 -1
  40. package/dist/deltaScheduler.js.map +1 -0
  41. package/dist/{error.cjs → error.js} +1 -1
  42. package/dist/error.js.map +1 -0
  43. package/dist/gc/garbageCollection.d.ts +17 -1
  44. package/dist/gc/garbageCollection.d.ts.map +1 -1
  45. package/dist/gc/{garbageCollection.cjs → garbageCollection.js} +111 -42
  46. package/dist/gc/garbageCollection.js.map +1 -0
  47. package/dist/gc/{gcConfigs.cjs → gcConfigs.js} +3 -3
  48. package/dist/gc/gcConfigs.js.map +1 -0
  49. package/dist/gc/gcDefinitions.d.ts +20 -2
  50. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  51. package/dist/gc/{gcDefinitions.cjs → gcDefinitions.js} +8 -2
  52. package/dist/gc/gcDefinitions.js.map +1 -0
  53. package/dist/gc/{gcHelpers.cjs → gcHelpers.js} +1 -1
  54. package/dist/gc/gcHelpers.js.map +1 -0
  55. package/dist/gc/{gcReferenceGraphAlgorithm.cjs → gcReferenceGraphAlgorithm.js} +1 -1
  56. package/dist/gc/gcReferenceGraphAlgorithm.js.map +1 -0
  57. package/dist/gc/{gcSummaryDefinitions.cjs → gcSummaryDefinitions.js} +1 -1
  58. package/dist/gc/gcSummaryDefinitions.js.map +1 -0
  59. package/dist/gc/{gcSummaryStateTracker.cjs → gcSummaryStateTracker.js} +2 -2
  60. package/dist/gc/gcSummaryStateTracker.js.map +1 -0
  61. package/dist/gc/gcTelemetry.d.ts +1 -0
  62. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  63. package/dist/gc/{gcTelemetry.cjs → gcTelemetry.js} +2 -4
  64. package/dist/gc/gcTelemetry.js.map +1 -0
  65. package/dist/gc/gcUnreferencedStateTracker.d.ts +5 -0
  66. package/dist/gc/gcUnreferencedStateTracker.d.ts.map +1 -1
  67. package/dist/gc/{gcUnreferencedStateTracker.cjs → gcUnreferencedStateTracker.js} +14 -3
  68. package/dist/gc/{gcUnreferencedStateTracker.cjs.map → gcUnreferencedStateTracker.js.map} +1 -1
  69. package/dist/gc/index.d.ts +1 -1
  70. package/dist/gc/index.d.ts.map +1 -1
  71. package/dist/gc/{index.cjs → index.js} +11 -9
  72. package/dist/gc/index.js.map +1 -0
  73. package/dist/index.d.ts +2 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/{index.cjs → index.js} +10 -8
  76. package/dist/index.js.map +1 -0
  77. package/dist/messageTypes.d.ts +1 -1
  78. package/dist/{messageTypes.cjs → messageTypes.js} +1 -1
  79. package/dist/messageTypes.js.map +1 -0
  80. package/dist/{metadata.cjs → metadata.js} +1 -1
  81. package/dist/metadata.js.map +1 -0
  82. package/dist/opLifecycle/{batchManager.cjs → batchManager.js} +1 -1
  83. package/dist/opLifecycle/batchManager.js.map +1 -0
  84. package/dist/opLifecycle/{definitions.cjs → definitions.js} +1 -1
  85. package/dist/opLifecycle/definitions.js.map +1 -0
  86. package/dist/opLifecycle/{index.cjs → index.js} +8 -8
  87. package/dist/opLifecycle/index.js.map +1 -0
  88. package/dist/opLifecycle/{opCompressor.cjs → opCompressor.js} +3 -3
  89. package/dist/opLifecycle/opCompressor.js.map +1 -0
  90. package/dist/opLifecycle/{opDecompressor.cjs → opDecompressor.js} +2 -2
  91. package/dist/opLifecycle/opDecompressor.js.map +1 -0
  92. package/dist/opLifecycle/{opGroupingManager.cjs → opGroupingManager.js} +1 -1
  93. package/dist/opLifecycle/opGroupingManager.js.map +1 -0
  94. package/dist/opLifecycle/{opSplitter.cjs → opSplitter.js} +3 -3
  95. package/dist/opLifecycle/opSplitter.js.map +1 -0
  96. package/dist/opLifecycle/{outbox.cjs → outbox.js} +2 -2
  97. package/dist/opLifecycle/outbox.js.map +1 -0
  98. package/dist/opLifecycle/{remoteMessageProcessor.cjs → remoteMessageProcessor.js} +2 -2
  99. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
  100. package/dist/{opProperties.cjs → opProperties.js} +1 -1
  101. package/dist/opProperties.js.map +1 -0
  102. package/dist/packageVersion.d.ts +1 -1
  103. package/dist/{packageVersion.cjs → packageVersion.js} +2 -2
  104. package/dist/packageVersion.js.map +1 -0
  105. package/dist/{pendingStateManager.cjs → pendingStateManager.js} +2 -2
  106. package/dist/pendingStateManager.js.map +1 -0
  107. package/dist/{scheduleManager.cjs → scheduleManager.js} +3 -3
  108. package/dist/scheduleManager.js.map +1 -0
  109. package/dist/{storageServiceWithAttachBlobs.cjs → storageServiceWithAttachBlobs.js} +1 -1
  110. package/dist/storageServiceWithAttachBlobs.js.map +1 -0
  111. package/dist/summary/{index.cjs → index.js} +12 -12
  112. package/dist/summary/index.js.map +1 -0
  113. package/dist/summary/{orderedClientElection.cjs → orderedClientElection.js} +2 -2
  114. package/dist/summary/orderedClientElection.js.map +1 -0
  115. package/dist/summary/{runWhileConnectedCoordinator.cjs → runWhileConnectedCoordinator.js} +1 -1
  116. package/dist/summary/runWhileConnectedCoordinator.js.map +1 -0
  117. package/dist/summary/runningSummarizer.d.ts +11 -6
  118. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  119. package/dist/summary/{runningSummarizer.cjs → runningSummarizer.js} +140 -93
  120. package/dist/summary/runningSummarizer.js.map +1 -0
  121. package/dist/summary/{summarizer.cjs → summarizer.js} +4 -4
  122. package/dist/summary/summarizer.js.map +1 -0
  123. package/dist/summary/{summarizerClientElection.cjs → summarizerClientElection.js} +1 -1
  124. package/dist/summary/summarizerClientElection.js.map +1 -0
  125. package/dist/summary/{summarizerHeuristics.cjs → summarizerHeuristics.js} +1 -1
  126. package/dist/summary/summarizerHeuristics.js.map +1 -0
  127. package/dist/summary/summarizerNode/{index.cjs → index.js} +3 -3
  128. package/dist/summary/summarizerNode/index.js.map +1 -0
  129. package/dist/summary/summarizerNode/summarizerNode.d.ts +2 -3
  130. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  131. package/dist/summary/summarizerNode/{summarizerNode.cjs → summarizerNode.js} +8 -50
  132. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -0
  133. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +1 -18
  134. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  135. package/dist/summary/summarizerNode/{summarizerNodeUtils.cjs → summarizerNodeUtils.js} +2 -22
  136. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -0
  137. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -3
  138. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  139. package/dist/summary/summarizerNode/{summarizerNodeWithGc.cjs → summarizerNodeWithGc.js} +9 -9
  140. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -0
  141. package/dist/summary/summarizerTypes.d.ts +3 -1
  142. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  143. package/dist/summary/{summarizerTypes.cjs → summarizerTypes.js} +1 -1
  144. package/dist/summary/summarizerTypes.js.map +1 -0
  145. package/dist/summary/{summaryCollection.cjs → summaryCollection.js} +1 -1
  146. package/dist/summary/summaryCollection.js.map +1 -0
  147. package/dist/summary/{summaryFormat.cjs → summaryFormat.js} +1 -1
  148. package/dist/summary/summaryFormat.js.map +1 -0
  149. package/dist/summary/{summaryGenerator.cjs → summaryGenerator.js} +1 -1
  150. package/dist/summary/summaryGenerator.js.map +1 -0
  151. package/dist/summary/{summaryManager.cjs → summaryManager.js} +2 -2
  152. package/dist/summary/summaryManager.js.map +1 -0
  153. package/dist/{throttler.cjs → throttler.js} +1 -1
  154. package/dist/throttler.js.map +1 -0
  155. package/dist/tsdoc-metadata.json +1 -1
  156. package/lib/blobManager.d.mts.map +1 -1
  157. package/lib/blobManager.mjs +7 -1
  158. package/lib/blobManager.mjs.map +1 -1
  159. package/lib/container-runtime-alpha.d.mts +13 -3
  160. package/lib/container-runtime-beta.d.mts +7 -0
  161. package/lib/container-runtime-public.d.mts +7 -0
  162. package/lib/container-runtime-untrimmed.d.mts +30 -3
  163. package/lib/containerRuntime.d.mts +5 -2
  164. package/lib/containerRuntime.d.mts.map +1 -1
  165. package/lib/containerRuntime.mjs +62 -69
  166. package/lib/containerRuntime.mjs.map +1 -1
  167. package/lib/dataStoreContext.d.mts +7 -1
  168. package/lib/dataStoreContext.d.mts.map +1 -1
  169. package/lib/dataStoreContext.mjs +11 -5
  170. package/lib/dataStoreContext.mjs.map +1 -1
  171. package/lib/dataStores.d.mts +9 -3
  172. package/lib/dataStores.d.mts.map +1 -1
  173. package/lib/dataStores.mjs +54 -9
  174. package/lib/dataStores.mjs.map +1 -1
  175. package/lib/gc/garbageCollection.d.mts +17 -1
  176. package/lib/gc/garbageCollection.d.mts.map +1 -1
  177. package/lib/gc/garbageCollection.mjs +103 -34
  178. package/lib/gc/garbageCollection.mjs.map +1 -1
  179. package/lib/gc/gcDefinitions.d.mts +20 -2
  180. package/lib/gc/gcDefinitions.d.mts.map +1 -1
  181. package/lib/gc/gcDefinitions.mjs +6 -0
  182. package/lib/gc/gcDefinitions.mjs.map +1 -1
  183. package/lib/gc/gcTelemetry.d.mts +1 -0
  184. package/lib/gc/gcTelemetry.d.mts.map +1 -1
  185. package/lib/gc/gcTelemetry.mjs +0 -2
  186. package/lib/gc/gcTelemetry.mjs.map +1 -1
  187. package/lib/gc/gcUnreferencedStateTracker.d.mts +5 -0
  188. package/lib/gc/gcUnreferencedStateTracker.d.mts.map +1 -1
  189. package/lib/gc/gcUnreferencedStateTracker.mjs +10 -0
  190. package/lib/gc/gcUnreferencedStateTracker.mjs.map +1 -1
  191. package/lib/gc/index.d.mts +1 -1
  192. package/lib/gc/index.d.mts.map +1 -1
  193. package/lib/gc/index.mjs +1 -1
  194. package/lib/gc/index.mjs.map +1 -1
  195. package/lib/index.d.mts +2 -1
  196. package/lib/index.d.mts.map +1 -1
  197. package/lib/index.mjs +1 -0
  198. package/lib/index.mjs.map +1 -1
  199. package/lib/messageTypes.d.mts +1 -1
  200. package/lib/messageTypes.mjs.map +1 -1
  201. package/lib/packageVersion.d.mts +1 -1
  202. package/lib/packageVersion.mjs +1 -1
  203. package/lib/packageVersion.mjs.map +1 -1
  204. package/lib/summary/runningSummarizer.d.mts +11 -6
  205. package/lib/summary/runningSummarizer.d.mts.map +1 -1
  206. package/lib/summary/runningSummarizer.mjs +136 -89
  207. package/lib/summary/runningSummarizer.mjs.map +1 -1
  208. package/lib/summary/summarizerNode/summarizerNode.d.mts +2 -3
  209. package/lib/summary/summarizerNode/summarizerNode.d.mts.map +1 -1
  210. package/lib/summary/summarizerNode/summarizerNode.mjs +8 -50
  211. package/lib/summary/summarizerNode/summarizerNode.mjs.map +1 -1
  212. package/lib/summary/summarizerNode/summarizerNodeUtils.d.mts +1 -18
  213. package/lib/summary/summarizerNode/summarizerNodeUtils.d.mts.map +1 -1
  214. package/lib/summary/summarizerNode/summarizerNodeUtils.mjs +0 -19
  215. package/lib/summary/summarizerNode/summarizerNodeUtils.mjs.map +1 -1
  216. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.mts +3 -3
  217. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.mts.map +1 -1
  218. package/lib/summary/summarizerNode/summarizerNodeWithGc.mjs +5 -5
  219. package/lib/summary/summarizerNode/summarizerNodeWithGc.mjs.map +1 -1
  220. package/lib/summary/summarizerTypes.d.mts +3 -1
  221. package/lib/summary/summarizerTypes.d.mts.map +1 -1
  222. package/lib/summary/summarizerTypes.mjs.map +1 -1
  223. package/package.json +48 -38
  224. package/src/blobManager.ts +7 -1
  225. package/src/containerRuntime.ts +103 -85
  226. package/src/dataStoreContext.ts +15 -6
  227. package/src/dataStores.ts +64 -6
  228. package/src/gc/garbageCollection.ts +118 -32
  229. package/src/gc/gcDefinitions.ts +21 -3
  230. package/src/gc/gcTelemetry.ts +1 -2
  231. package/src/gc/gcUnreferencedStateTracker.ts +11 -0
  232. package/src/gc/index.ts +3 -0
  233. package/src/index.ts +2 -0
  234. package/src/messageTypes.ts +1 -1
  235. package/src/packageVersion.ts +1 -1
  236. package/src/summary/runningSummarizer.ts +174 -113
  237. package/src/summary/summarizerNode/summarizerNode.ts +4 -64
  238. package/src/summary/summarizerNode/summarizerNodeUtils.ts +2 -33
  239. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +0 -6
  240. package/src/summary/summarizerTypes.ts +3 -1
  241. package/dist/batchTracker.cjs.map +0 -1
  242. package/dist/blobManager.cjs.map +0 -1
  243. package/dist/connectionTelemetry.cjs.map +0 -1
  244. package/dist/containerHandleContext.cjs.map +0 -1
  245. package/dist/containerRuntime.cjs.map +0 -1
  246. package/dist/dataStore.cjs.map +0 -1
  247. package/dist/dataStoreContext.cjs.map +0 -1
  248. package/dist/dataStoreContexts.cjs.map +0 -1
  249. package/dist/dataStoreRegistry.cjs.map +0 -1
  250. package/dist/dataStores.cjs.map +0 -1
  251. package/dist/deltaManagerProxyBase.cjs.map +0 -1
  252. package/dist/deltaManagerSummarizerProxy.cjs.map +0 -1
  253. package/dist/deltaScheduler.cjs.map +0 -1
  254. package/dist/error.cjs.map +0 -1
  255. package/dist/gc/garbageCollection.cjs.map +0 -1
  256. package/dist/gc/gcConfigs.cjs.map +0 -1
  257. package/dist/gc/gcDefinitions.cjs.map +0 -1
  258. package/dist/gc/gcHelpers.cjs.map +0 -1
  259. package/dist/gc/gcReferenceGraphAlgorithm.cjs.map +0 -1
  260. package/dist/gc/gcSummaryDefinitions.cjs.map +0 -1
  261. package/dist/gc/gcSummaryStateTracker.cjs.map +0 -1
  262. package/dist/gc/gcTelemetry.cjs.map +0 -1
  263. package/dist/gc/index.cjs.map +0 -1
  264. package/dist/index.cjs.map +0 -1
  265. package/dist/messageTypes.cjs.map +0 -1
  266. package/dist/metadata.cjs.map +0 -1
  267. package/dist/opLifecycle/batchManager.cjs.map +0 -1
  268. package/dist/opLifecycle/definitions.cjs.map +0 -1
  269. package/dist/opLifecycle/index.cjs.map +0 -1
  270. package/dist/opLifecycle/opCompressor.cjs.map +0 -1
  271. package/dist/opLifecycle/opDecompressor.cjs.map +0 -1
  272. package/dist/opLifecycle/opGroupingManager.cjs.map +0 -1
  273. package/dist/opLifecycle/opSplitter.cjs.map +0 -1
  274. package/dist/opLifecycle/outbox.cjs.map +0 -1
  275. package/dist/opLifecycle/remoteMessageProcessor.cjs.map +0 -1
  276. package/dist/opProperties.cjs.map +0 -1
  277. package/dist/packageVersion.cjs.map +0 -1
  278. package/dist/pendingStateManager.cjs.map +0 -1
  279. package/dist/scheduleManager.cjs.map +0 -1
  280. package/dist/storageServiceWithAttachBlobs.cjs.map +0 -1
  281. package/dist/summary/index.cjs.map +0 -1
  282. package/dist/summary/orderedClientElection.cjs.map +0 -1
  283. package/dist/summary/runWhileConnectedCoordinator.cjs.map +0 -1
  284. package/dist/summary/runningSummarizer.cjs.map +0 -1
  285. package/dist/summary/summarizer.cjs.map +0 -1
  286. package/dist/summary/summarizerClientElection.cjs.map +0 -1
  287. package/dist/summary/summarizerHeuristics.cjs.map +0 -1
  288. package/dist/summary/summarizerNode/index.cjs.map +0 -1
  289. package/dist/summary/summarizerNode/summarizerNode.cjs.map +0 -1
  290. package/dist/summary/summarizerNode/summarizerNodeUtils.cjs.map +0 -1
  291. package/dist/summary/summarizerNode/summarizerNodeWithGc.cjs.map +0 -1
  292. package/dist/summary/summarizerTypes.cjs.map +0 -1
  293. package/dist/summary/summaryCollection.cjs.map +0 -1
  294. package/dist/summary/summaryFormat.cjs.map +0 -1
  295. package/dist/summary/summaryGenerator.cjs.map +0 -1
  296. package/dist/summary/summaryManager.cjs.map +0 -1
  297. package/dist/throttler.cjs.map +0 -1
  298. package/tsc-multi.test.json +0 -4
  299. /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -11,6 +11,7 @@ import {
11
11
  IRequest,
12
12
  IResponse,
13
13
  IProvideFluidHandleContext,
14
+ ISignalEnvelope,
14
15
  } from "@fluidframework/core-interfaces";
15
16
  import {
16
17
  IAudience,
@@ -46,12 +47,13 @@ import {
46
47
  ITelemetryLoggerExt,
47
48
  UsageError,
48
49
  LoggingError,
50
+ createSampledLogger,
51
+ IEventSampler,
49
52
  } from "@fluidframework/telemetry-utils";
50
53
  import {
51
54
  DriverHeader,
52
55
  FetchSource,
53
56
  IDocumentStorageService,
54
- ISummaryContext,
55
57
  } from "@fluidframework/driver-definitions";
56
58
  import { readAndParse } from "@fluidframework/driver-utils";
57
59
  import {
@@ -77,7 +79,6 @@ import {
77
79
  IGarbageCollectionData,
78
80
  IEnvelope,
79
81
  IInboundSignalMessage,
80
- ISignalEnvelope,
81
82
  NamedFluidDataStoreRegistryEntries,
82
83
  ISummaryTreeWithStats,
83
84
  ISummarizeInternalResult,
@@ -862,14 +863,34 @@ export class ContainerRuntime
862
863
  "@fluidframework/id-compressor"
863
864
  );
864
865
 
865
- const pendingLocalState = context.pendingLocalState as IPendingRuntimeState;
866
+ /**
867
+ * Because the IdCompressor emits so much telemetry, this function is used to sample
868
+ * approximately 5% of all clients. Only the given percentage of sessions will emit telemetry.
869
+ */
870
+ const idCompressorEventSampler: IEventSampler = (() => {
871
+ const isIdCompressorTelemetryEnabled = Math.random() < 0.05;
872
+ return {
873
+ sample: () => {
874
+ return isIdCompressorTelemetryEnabled;
875
+ },
876
+ };
877
+ })();
866
878
 
879
+ const compressorLogger = createSampledLogger(logger, idCompressorEventSampler);
880
+ const pendingLocalState = context.pendingLocalState as IPendingRuntimeState;
867
881
  if (pendingLocalState?.pendingIdCompressorState !== undefined) {
868
- idCompressor = deserializeIdCompressor(pendingLocalState.pendingIdCompressorState);
882
+ idCompressor = deserializeIdCompressor(
883
+ pendingLocalState.pendingIdCompressorState,
884
+ compressorLogger,
885
+ );
869
886
  } else if (serializedIdCompressor !== undefined) {
870
- idCompressor = deserializeIdCompressor(serializedIdCompressor, createSessionId());
887
+ idCompressor = deserializeIdCompressor(
888
+ serializedIdCompressor,
889
+ createSessionId(),
890
+ compressorLogger,
891
+ );
871
892
  } else {
872
- idCompressor = createIdCompressor(logger);
893
+ idCompressor = createIdCompressor(compressorLogger);
873
894
  }
874
895
  }
875
896
 
@@ -1371,15 +1392,17 @@ export class ContainerRuntime
1371
1392
 
1372
1393
  const pendingRuntimeState = pendingLocalState as IPendingRuntimeState | undefined;
1373
1394
 
1374
- const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
1375
- if (
1376
- maxSnapshotCacheDurationMs !== undefined &&
1377
- maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000
1378
- ) {
1379
- // This is a runtime enforcement of what's already explicit in the policy's type itself,
1380
- // which dictates the value is either undefined or exactly 5 days in ms.
1381
- // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
1382
- throw new UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
1395
+ if (context.attachState === AttachState.Attached) {
1396
+ const maxSnapshotCacheDurationMs = this._storage?.policies?.maximumCacheDurationMs;
1397
+ if (
1398
+ maxSnapshotCacheDurationMs !== undefined &&
1399
+ maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000
1400
+ ) {
1401
+ // This is a runtime enforcement of what's already explicit in the policy's type itself,
1402
+ // which dictates the value is either undefined or exactly 5 days in ms.
1403
+ // As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
1404
+ throw new UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
1405
+ }
1383
1406
  }
1384
1407
 
1385
1408
  this.garbageCollector = GarbageCollector.create({
@@ -1411,9 +1434,6 @@ export class ContainerRuntime
1411
1434
  // Must set to false to prevent sending summary handle which would be pointing to
1412
1435
  // a summary with an older protocol state.
1413
1436
  canReuseHandle: false,
1414
- // Must set to true to throw on any data stores failure that was too severe to be handled.
1415
- // We also are not decoding the base summaries at the root.
1416
- throwOnFailure: true,
1417
1437
  // If GC should not run, let the summarizer node know so that it does not track GC state.
1418
1438
  gcDisabled: !this.garbageCollector.shouldRunGC,
1419
1439
  },
@@ -2054,6 +2074,8 @@ export class ContainerRuntime
2054
2074
  this.closeFn(error);
2055
2075
  throw error;
2056
2076
  }
2077
+ // Note: Even if its compat behavior allows it, we don't know how to apply this stashed op.
2078
+ // All we can do is ignore it (similar to on process).
2057
2079
  }
2058
2080
  }
2059
2081
  }
@@ -2268,6 +2290,7 @@ export class ContainerRuntime
2268
2290
  messageWithContext.message,
2269
2291
  local,
2270
2292
  localOpMetadata,
2293
+ (from, to) => this.garbageCollector.addedOutboundReference(from, to),
2271
2294
  );
2272
2295
  break;
2273
2296
  case ContainerMessageType.BlobAttach:
@@ -2407,6 +2430,9 @@ export class ContainerRuntime
2407
2430
  assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
2408
2431
  }
2409
2432
 
2433
+ /**
2434
+ * {@inheritDoc @fluidframework/runtime-definitions#IContainerRuntimeBase.orderSequentially}
2435
+ */
2410
2436
  public orderSequentially<T>(callback: () => T): T {
2411
2437
  let checkpoint: IBatchCheckpoint | undefined;
2412
2438
  let result: T;
@@ -2438,9 +2464,21 @@ export class ContainerRuntime
2438
2464
  throw error2;
2439
2465
  }
2440
2466
  } else {
2441
- // pre-0.58 error message: orderSequentiallyCallbackException
2442
- this.closeFn(new GenericError("orderSequentially callback exception", error));
2467
+ this.closeFn(
2468
+ wrapError(
2469
+ error,
2470
+ (errorMessage) =>
2471
+ new GenericError(
2472
+ `orderSequentially callback exception: ${errorMessage}`,
2473
+ error,
2474
+ {
2475
+ orderSequentiallyCalls: this._orderSequentiallyCalls,
2476
+ },
2477
+ ),
2478
+ ),
2479
+ );
2443
2480
  }
2481
+
2444
2482
  throw error; // throw the original error for the consumer of the runtime
2445
2483
  } finally {
2446
2484
  this._orderSequentiallyCalls--;
@@ -2495,15 +2533,23 @@ export class ContainerRuntime
2495
2533
  return this.dataStores.createDetachedDataStoreCore(pkg, true, rootDataStoreId);
2496
2534
  }
2497
2535
 
2498
- public createDetachedDataStore(pkg: Readonly<string[]>): IFluidDataStoreContextDetached {
2499
- return this.dataStores.createDetachedDataStoreCore(pkg, false);
2536
+ public createDetachedDataStore(
2537
+ pkg: Readonly<string[]>,
2538
+ groupId?: string,
2539
+ ): IFluidDataStoreContextDetached {
2540
+ return this.dataStores.createDetachedDataStoreCore(pkg, false, undefined, groupId);
2500
2541
  }
2501
2542
 
2502
- public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
2543
+ public async createDataStore(pkg: string | string[], groupId?: string): Promise<IDataStore> {
2503
2544
  const id = uuid();
2504
2545
  return channelToDataStore(
2505
2546
  await this.dataStores
2506
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id)
2547
+ ._createFluidDataStoreContext(
2548
+ Array.isArray(pkg) ? pkg : [pkg],
2549
+ id,
2550
+ undefined,
2551
+ groupId,
2552
+ )
2507
2553
  .realize(),
2508
2554
  id,
2509
2555
  this,
@@ -2679,6 +2725,12 @@ export class ContainerRuntime
2679
2725
  this.blobManager.setRedirectTable(blobRedirectTable);
2680
2726
  }
2681
2727
 
2728
+ // We can finalize any allocated IDs since we're the only client
2729
+ const idRange = this.idCompressor?.takeNextCreationRange();
2730
+ if (idRange !== undefined) {
2731
+ this.idCompressor?.finalizeCreationRange(idRange);
2732
+ }
2733
+
2682
2734
  const summarizeResult = this.dataStores.createSummary(telemetryContext);
2683
2735
  // Wrap data store summaries in .channels subtree.
2684
2736
  wrapSummaryInChannelsTree(summarizeResult);
@@ -2898,6 +2950,12 @@ export class ContainerRuntime
2898
2950
  * data store or an attachment blob.
2899
2951
  */
2900
2952
  public async getGCNodePackagePath(nodePath: string): Promise<readonly string[] | undefined> {
2953
+ // GC uses "/" when adding "root" references, e.g. for Aliasing or as part of Tombstone Auto-Recovery.
2954
+ // These have no package path so return a special value.
2955
+ if (nodePath === "/") {
2956
+ return ["<GCROOT>"];
2957
+ }
2958
+
2901
2959
  switch (this.getNodeType(nodePath)) {
2902
2960
  case GCNodeType.Blob:
2903
2961
  return [BlobManager.basePath];
@@ -2993,7 +3051,6 @@ export class ContainerRuntime
2993
3051
  assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2994
3052
 
2995
3053
  // We close the summarizer and download a new snapshot and reload the container
2996
- let latestSnapshotVersionId: string | undefined;
2997
3054
  if (refreshLatestAck === true) {
2998
3055
  return this.prefetchLatestSummaryThenClose(
2999
3056
  createChildLogger({
@@ -3209,34 +3266,18 @@ export class ContainerRuntime
3209
3266
  return { stage: "generate", ...generateSummaryData, error: continueResult.error };
3210
3267
  }
3211
3268
 
3212
- // It may happen that the lastAck it not correct due to missing summaryAck in case of single commit
3213
- // summary. So if the previous summarizer closes just after submitting the summary and before
3214
- // submitting the summaryOp then we can't rely on summaryAck. So in case we have
3215
- // latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
3216
- // the one fetched from storage as parent as that is the latest.
3217
- let summaryContext: ISummaryContext;
3218
- if (
3219
- lastAck?.summaryAck.contents.handle !== latestSnapshotVersionId &&
3220
- latestSnapshotVersionId !== undefined
3221
- ) {
3222
- summaryContext = {
3223
- proposalHandle: undefined,
3224
- ackHandle: latestSnapshotVersionId,
3225
- referenceSequenceNumber: summaryRefSeqNum,
3226
- };
3227
- } else if (lastAck === undefined) {
3228
- summaryContext = {
3229
- proposalHandle: undefined,
3230
- ackHandle: this.loadedFromVersionId,
3231
- referenceSequenceNumber: summaryRefSeqNum,
3232
- };
3233
- } else {
3234
- summaryContext = {
3235
- proposalHandle: lastAck.summaryOp.contents.handle,
3236
- ackHandle: lastAck.summaryAck.contents.handle,
3237
- referenceSequenceNumber: summaryRefSeqNum,
3238
- };
3239
- }
3269
+ const summaryContext =
3270
+ lastAck === undefined
3271
+ ? {
3272
+ proposalHandle: undefined,
3273
+ ackHandle: this.loadedFromVersionId,
3274
+ referenceSequenceNumber: summaryRefSeqNum,
3275
+ }
3276
+ : {
3277
+ proposalHandle: lastAck.summaryOp.contents.handle,
3278
+ ackHandle: lastAck.summaryAck.contents.handle,
3279
+ referenceSequenceNumber: summaryRefSeqNum,
3280
+ };
3240
3281
 
3241
3282
  let handle: string;
3242
3283
  try {
@@ -3680,6 +3721,7 @@ export class ContainerRuntime
3680
3721
  localOpMetadata: unknown,
3681
3722
  opMetadata: Record<string, unknown> | undefined,
3682
3723
  ) {
3724
+ assert(!this.isSummarizerClient, "Summarizer never reconnects so should never resubmit");
3683
3725
  switch (message.type) {
3684
3726
  case ContainerMessageType.FluidDataStoreOp:
3685
3727
  // For Operations, call resubmitDataStoreOp which will find the right store
@@ -3701,13 +3743,14 @@ export class ContainerRuntime
3701
3743
  this.submit(message);
3702
3744
  break;
3703
3745
  case ContainerMessageType.GC:
3704
- // GC op is only sent in summarizer which should never reconnect.
3705
- throw new LoggingError("GC op not expected to be resubmitted in summarizer");
3746
+ this.submit(message);
3747
+ break;
3706
3748
  default: {
3707
3749
  // This case should be very rare - it would imply an op was stashed from a
3708
- // future version of runtime code and now is being applied on an older version
3750
+ // future version of runtime code and now is being applied on an older version.
3709
3751
  const compatBehavior = message.compatDetails?.behavior;
3710
3752
  if (compatBehaviorAllowsMessageType(message.type, compatBehavior)) {
3753
+ // We do not ultimately resubmit it, to be consistent with this version of the code.
3711
3754
  this.logger.sendTelemetryEvent({
3712
3755
  eventName: "resubmitUnrecognizedMessageTypeAllowed",
3713
3756
  messageDetails: { type: message.type, compatBehavior },
@@ -3764,7 +3807,7 @@ export class ContainerRuntime
3764
3807
  * and then close as the current main client is likely to be re-elected as the parent summarizer again.
3765
3808
  */
3766
3809
  if (!result.isSummaryTracked && result.isSummaryNewer) {
3767
- const fetchResult = await this.fetchLatestSnapshotFromStorage(
3810
+ await this.fetchLatestSnapshotFromStorage(
3768
3811
  summaryLogger,
3769
3812
  {
3770
3813
  eventName: "RefreshLatestSummaryAckFetch",
@@ -3774,32 +3817,7 @@ export class ContainerRuntime
3774
3817
  readAndParseBlob,
3775
3818
  );
3776
3819
 
3777
- /**
3778
- * If the fetched snapshot is older than the one for which the ack was received, close the container.
3779
- * This should never happen because an ack should be sent after the latest summary is updated in the server.
3780
- * However, there are couple of scenarios where it's possible:
3781
- * 1. A file was modified externally resulting in modifying the snapshot's sequence number. This can lead to
3782
- * the document being unusable and we should not proceed.
3783
- * 2. The server DB failed after the ack was sent which may delete the corresponding snapshot. Ideally, in
3784
- * such cases, the file will be rolled back along with the ack and we will eventually reach a consistent
3785
- * state.
3786
- */
3787
- if (fetchResult.latestSnapshotRefSeq < summaryRefSeq) {
3788
- const error = DataProcessingError.create(
3789
- "Fetched snapshot is older than the received ack",
3790
- "RefreshLatestSummaryAck",
3791
- undefined /* sequencedMessage */,
3792
- {
3793
- ackHandle,
3794
- summaryRefSeq,
3795
- fetchedSnapshotRefSeq: fetchResult.latestSnapshotRefSeq,
3796
- },
3797
- );
3798
- this.disposeFn(error);
3799
- throw error;
3800
- }
3801
-
3802
- await this.closeStaleSummarizer("RefreshLatestSummaryAckFetch");
3820
+ await this.closeStaleSummarizer();
3803
3821
  return;
3804
3822
  }
3805
3823
 
@@ -3828,7 +3846,7 @@ export class ContainerRuntime
3828
3846
  readAndParseBlob,
3829
3847
  );
3830
3848
 
3831
- await this.closeStaleSummarizer("RefreshLatestSummaryFromServerFetch");
3849
+ await this.closeStaleSummarizer();
3832
3850
 
3833
3851
  return {
3834
3852
  stage: "base",
@@ -3838,7 +3856,7 @@ export class ContainerRuntime
3838
3856
  };
3839
3857
  }
3840
3858
 
3841
- private async closeStaleSummarizer(codePath: string): Promise<void> {
3859
+ private async closeStaleSummarizer(): Promise<void> {
3842
3860
  // Delay before restarting summarizer to prevent the summarizer from restarting too frequently.
3843
3861
  await delay(this.closeSummarizerDelayMs);
3844
3862
  this._summarizer?.stop("latestSummaryStateStale");
@@ -50,8 +50,6 @@ import {
50
50
  ISummarizerNodeWithGC,
51
51
  SummarizeInternalFn,
52
52
  ITelemetryContext,
53
- IIdCompressor,
54
- IIdCompressorCore,
55
53
  VisibilityState,
56
54
  } from "@fluidframework/runtime-definitions";
57
55
  import { addBlobToSummary, convertSummaryTreeToITree } from "@fluidframework/runtime-utils";
@@ -67,6 +65,7 @@ import {
67
65
  tagCodeArtifacts,
68
66
  ThresholdCounter,
69
67
  } from "@fluidframework/telemetry-utils";
68
+ import { IIdCompressor, IIdCompressorCore } from "@fluidframework/id-compressor";
70
69
  import {
71
70
  dataStoreAttributesBlobName,
72
71
  hasIsolatedChannels,
@@ -78,7 +77,7 @@ import {
78
77
  summarizerClientType,
79
78
  } from "./summary";
80
79
  import { ContainerRuntime } from "./containerRuntime";
81
- import { sendGCUnexpectedUsageEvent } from "./gc";
80
+ import { detectOutboundRoutesViaDDSKey, sendGCUnexpectedUsageEvent } from "./gc";
82
81
 
83
82
  function createAttributes(
84
83
  pkg: readonly string[],
@@ -115,6 +114,7 @@ export interface IFluidDataStoreContextProps {
115
114
  readonly scope: FluidObject;
116
115
  readonly createSummarizerNodeFn: CreateChildSummarizerNodeFn;
117
116
  readonly pkg?: Readonly<string[]>;
117
+ readonly groupId?: string;
118
118
  }
119
119
 
120
120
  /** Properties necessary for creating a local FluidDataStoreContext */
@@ -272,6 +272,8 @@ export abstract class FluidDataStoreContext
272
272
  private readonly _containerRuntime: ContainerRuntime;
273
273
  public readonly storage: IDocumentStorageService;
274
274
  public readonly scope: FluidObject;
275
+ // Represents the group to which the data store belongs too.
276
+ public readonly groupId: string | undefined;
275
277
  protected pkg?: readonly string[];
276
278
 
277
279
  constructor(
@@ -287,6 +289,7 @@ export abstract class FluidDataStoreContext
287
289
  this.storage = props.storage;
288
290
  this.scope = props.scope;
289
291
  this.pkg = props.pkg;
292
+ this.groupId = props.groupId;
290
293
 
291
294
  // URIs use slashes as delimiters. Handles use URIs.
292
295
  // Thus having slashes in types almost guarantees trouble down the road!
@@ -650,13 +653,20 @@ export abstract class FluidDataStoreContext
650
653
  }
651
654
 
652
655
  /**
656
+ * @deprecated There is no replacement for this, its functionality is no longer needed.
657
+ * It will be removed in a future release, sometime after 2.0.0-internal.8.0.0
658
+ *
653
659
  * Called when a new outbound reference is added to another node. This is used by garbage collection to identify
654
660
  * all references added in the system.
655
661
  * @param srcHandle - The handle of the node that added the reference.
656
662
  * @param outboundHandle - The handle of the outbound node that is referenced.
657
663
  */
658
664
  public addedGCOutboundReference(srcHandle: IFluidHandle, outboundHandle: IFluidHandle) {
659
- this._containerRuntime.addedGCOutboundReference(srcHandle, outboundHandle);
665
+ // By default, skip this call since the ContainerRuntime will detect the outbound route directly.
666
+ if (this.mc.config.getBoolean(detectOutboundRoutesViaDDSKey) === true) {
667
+ // Note: The ContainerRuntime code will check this same setting to avoid double counting.
668
+ this._containerRuntime.addedGCOutboundReference(srcHandle, outboundHandle);
669
+ }
660
670
  }
661
671
 
662
672
  /**
@@ -941,8 +951,7 @@ export abstract class FluidDataStoreContext
941
951
  summarizeInternal,
942
952
  id,
943
953
  createParam,
944
- // DDS will not create failure summaries
945
- { throwOnFailure: true },
954
+ undefined /* config */,
946
955
  getGCDataFn,
947
956
  );
948
957
  }
package/src/dataStores.ts CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  create404Response,
34
34
  createResponseError,
35
35
  GCDataBuilder,
36
+ isSerializedHandle,
36
37
  responseToException,
37
38
  SummaryTreeBuilder,
38
39
  unpackChildNodesUsedRoutes,
@@ -61,7 +62,7 @@ import {
61
62
  } from "./dataStoreContext";
62
63
  import { StorageServiceWithAttachBlobs } from "./storageServiceWithAttachBlobs";
63
64
  import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
64
- import { GCNodeType, disableDatastoreSweepKey } from "./gc";
65
+ import { GCNodeType, detectOutboundRoutesViaDDSKey, disableDatastoreSweepKey } from "./gc";
65
66
  import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summary";
66
67
 
67
68
  type PendingAliasResolve = (success: boolean) => void;
@@ -152,6 +153,7 @@ export class DataStores implements IDisposable {
152
153
  createSummarizerNodeFn: this.getCreateChildSummarizerNodeFn(key, {
153
154
  type: CreateSummarizerNodeSource.FromSummary,
154
155
  }),
156
+ groupId: value.groupId,
155
157
  });
156
158
  } else {
157
159
  if (typeof value !== "object") {
@@ -238,6 +240,7 @@ export class DataStores implements IDisposable {
238
240
  runtime: this.runtime,
239
241
  storage: new StorageServiceWithAttachBlobs(this.runtime.storage, flatAttachBlobs),
240
242
  scope: this.runtime.scope,
243
+ groupId: snapshotTree?.groupId,
241
244
  createSummarizerNodeFn: this.getCreateChildSummarizerNodeFn(attachMessage.id, {
242
245
  type: CreateSummarizerNodeSource.FromAttach,
243
246
  sequenceNumber: message.sequenceNumber,
@@ -343,6 +346,7 @@ export class DataStores implements IDisposable {
343
346
  pkg: Readonly<string[]>,
344
347
  isRoot: boolean,
345
348
  id = uuid(),
349
+ groupId?: string,
346
350
  ): IFluidDataStoreContextDetached {
347
351
  assert(!id.includes("/"), 0x30c /* Id cannot contain slashes */);
348
352
 
@@ -358,12 +362,13 @@ export class DataStores implements IDisposable {
358
362
  makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
359
363
  snapshotTree: undefined,
360
364
  isRootDataStore: isRoot,
365
+ groupId,
361
366
  });
362
367
  this.contexts.addUnbound(context);
363
368
  return context;
364
369
  }
365
370
 
366
- public _createFluidDataStoreContext(pkg: string[], id: string, props?: any) {
371
+ public _createFluidDataStoreContext(pkg: string[], id: string, props?: any, groupId?: string) {
367
372
  assert(!id.includes("/"), 0x30d /* Id cannot contain slashes */);
368
373
  const context = new LocalFluidDataStoreContext({
369
374
  id,
@@ -378,6 +383,7 @@ export class DataStores implements IDisposable {
378
383
  snapshotTree: undefined,
379
384
  isRootDataStore: false,
380
385
  createProps: props,
386
+ groupId,
381
387
  });
382
388
  this.contexts.addUnbound(context);
383
389
  return context;
@@ -441,6 +447,7 @@ export class DataStores implements IDisposable {
441
447
  message: ISequencedDocumentMessage,
442
448
  local: boolean,
443
449
  localMessageMetadata: unknown,
450
+ addedOutboundReference: (fromNodePath: string, toNodePath: string) => void,
444
451
  ) {
445
452
  const envelope = message.contents as IEnvelope;
446
453
  const transformed = { ...message, contents: envelope.contents };
@@ -462,6 +469,13 @@ export class DataStores implements IDisposable {
462
469
  assert(!!context, 0x162 /* "There should be a store context for the op" */);
463
470
  context.process(transformed, local, localMessageMetadata);
464
471
 
472
+ // By default, we use the new behavior of detecting outbound routes here.
473
+ // If this setting is true, then DataStoreContext would be notifying GC instead.
474
+ if (this.mc.config.getBoolean(detectOutboundRoutesViaDDSKey) !== true) {
475
+ // Notify GC of any outbound references that were added by this op.
476
+ detectOutboundReferences(envelope, addedOutboundReference);
477
+ }
478
+
465
479
  // Notify that a GC node for the data store changed. This is used to detect if a deleted data store is
466
480
  // being used.
467
481
  this.gcNodeUpdated(
@@ -830,12 +844,15 @@ export class DataStores implements IDisposable {
830
844
 
831
845
  const dataStoreContext = this.contexts.get(dataStoreId);
832
846
  if (dataStoreContext === undefined) {
833
- this.mc.logger.sendErrorEvent({
847
+ // If the data store hasn't already been deleted, log an error because this should never happen.
848
+ // If the data store has already been deleted, log a telemetry event. This can happen because multiple GC
849
+ // sweep ops can contain the same data store. It would be interesting to track how often this happens.
850
+ const alreadyDeleted = this.isDataStoreDeleted(`/${dataStoreId}`);
851
+ this.mc.logger.sendTelemetryEvent({
834
852
  eventName: "DeletedDataStoreNotFound",
853
+ category: alreadyDeleted ? "generic" : "error",
835
854
  ...tagCodeArtifacts({ id: dataStoreId }),
836
- details: {
837
- alreadyDeleted: this.isDataStoreDeleted(dataStoreId),
838
- },
855
+ details: { alreadyDeleted },
839
856
  });
840
857
  continue;
841
858
  }
@@ -947,3 +964,44 @@ export function getSummaryForDatastores(
947
964
  };
948
965
  }
949
966
  }
967
+
968
+ /**
969
+ * Traverse this op's contents and detect any outbound routes that were added by this op.
970
+ *
971
+ * @internal
972
+ */
973
+ export function detectOutboundReferences(
974
+ envelope: IEnvelope,
975
+ addedOutboundReference: (fromNodePath: string, toNodePath: string) => void,
976
+ ): void {
977
+ // These will be built up as we traverse the envelope contents
978
+ const outboundPaths: string[] = [];
979
+ let ddsAddress: string | undefined;
980
+
981
+ function recursivelyFindHandles(obj: unknown) {
982
+ if (typeof obj === "object" && obj !== null) {
983
+ for (const [key, value] of Object.entries(obj)) {
984
+ // If 'value' is a serialized IFluidHandle, it represents a new outbound route.
985
+ if (isSerializedHandle(value)) {
986
+ outboundPaths.push(value.url);
987
+ }
988
+
989
+ // NOTE: This is taking a hard dependency on the fact that in our DataStore implementation,
990
+ // the address of the DDS is stored in a property called "address". This is not ideal.
991
+ // An alternative would be for the op envelope to include the absolute path (built up as it is submitted)
992
+ if (key === "address" && ddsAddress === undefined) {
993
+ ddsAddress = value;
994
+ }
995
+
996
+ recursivelyFindHandles(value);
997
+ }
998
+ }
999
+ }
1000
+
1001
+ recursivelyFindHandles(envelope.contents);
1002
+
1003
+ // GC node paths are all absolute paths, hence the "" prefix.
1004
+ // e.g. this will yield "/dataStoreId/ddsId"
1005
+ const fromPath = ["", envelope.address, ddsAddress].join("/");
1006
+ outboundPaths.forEach((toPath) => addedOutboundReference(fromPath, toPath));
1007
+ }