@fluidframework/container-loader 2.0.0-rc.1.0.6 → 2.0.0-rc.2.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 (298) hide show
  1. package/{.eslintrc.js → .eslintrc.cjs} +5 -6
  2. package/{.mocharc.js → .mocharc.cjs} +1 -1
  3. package/CHANGELOG.md +48 -0
  4. package/README.md +3 -3
  5. package/{api-extractor-esm.json → api-extractor-cjs.json} +5 -1
  6. package/api-extractor-lint.json +1 -1
  7. package/api-extractor.json +1 -1
  8. package/api-report/container-loader.api.md +2 -2
  9. package/dist/attachment.d.ts +115 -0
  10. package/dist/attachment.d.ts.map +1 -0
  11. package/dist/attachment.js +83 -0
  12. package/dist/attachment.js.map +1 -0
  13. package/dist/audience.d.ts +9 -4
  14. package/dist/audience.d.ts.map +1 -1
  15. package/dist/audience.js +10 -4
  16. package/dist/audience.js.map +1 -1
  17. package/dist/connectionManager.d.ts +3 -3
  18. package/dist/connectionManager.d.ts.map +1 -1
  19. package/dist/connectionManager.js +17 -18
  20. package/dist/connectionManager.js.map +1 -1
  21. package/dist/connectionState.d.ts +1 -0
  22. package/dist/connectionState.d.ts.map +1 -1
  23. package/dist/connectionState.js +1 -0
  24. package/dist/connectionState.js.map +1 -1
  25. package/dist/connectionStateHandler.d.ts +7 -7
  26. package/dist/connectionStateHandler.d.ts.map +1 -1
  27. package/dist/connectionStateHandler.js +32 -32
  28. package/dist/connectionStateHandler.js.map +1 -1
  29. package/dist/container-loader-alpha.d.ts +2 -1
  30. package/dist/container-loader-beta.d.ts +3 -0
  31. package/dist/container-loader-public.d.ts +3 -0
  32. package/dist/container-loader-untrimmed.d.ts +5 -5
  33. package/dist/container.d.ts +29 -27
  34. package/dist/container.d.ts.map +1 -1
  35. package/dist/container.js +215 -284
  36. package/dist/container.js.map +1 -1
  37. package/dist/containerContext.d.ts +3 -2
  38. package/dist/containerContext.d.ts.map +1 -1
  39. package/dist/containerContext.js +2 -1
  40. package/dist/containerContext.js.map +1 -1
  41. package/dist/containerStorageAdapter.d.ts +5 -6
  42. package/dist/containerStorageAdapter.d.ts.map +1 -1
  43. package/dist/containerStorageAdapter.js +14 -21
  44. package/dist/containerStorageAdapter.js.map +1 -1
  45. package/dist/contracts.d.ts +3 -3
  46. package/dist/contracts.d.ts.map +1 -1
  47. package/dist/contracts.js.map +1 -1
  48. package/dist/debugLogger.js.map +1 -1
  49. package/dist/deltaManager.d.ts +5 -5
  50. package/dist/deltaManager.d.ts.map +1 -1
  51. package/dist/deltaManager.js +6 -6
  52. package/dist/deltaManager.js.map +1 -1
  53. package/dist/error.d.ts.map +1 -1
  54. package/dist/error.js.map +1 -1
  55. package/dist/index.d.ts +6 -6
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +11 -11
  58. package/dist/index.js.map +1 -1
  59. package/dist/loader.d.ts +3 -3
  60. package/dist/loader.d.ts.map +1 -1
  61. package/dist/loader.js +13 -17
  62. package/dist/loader.js.map +1 -1
  63. package/dist/location-redirection-utilities/index.d.ts +1 -1
  64. package/dist/location-redirection-utilities/index.d.ts.map +1 -1
  65. package/dist/location-redirection-utilities/index.js +3 -3
  66. package/dist/location-redirection-utilities/index.js.map +1 -1
  67. package/dist/package.json +3 -0
  68. package/dist/packageVersion.d.ts +1 -1
  69. package/dist/packageVersion.js +1 -1
  70. package/dist/packageVersion.js.map +1 -1
  71. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  72. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  73. package/dist/protocolTreeDocumentStorageService.js +1 -3
  74. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  75. package/dist/retriableDocumentStorageService.d.ts +2 -2
  76. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  77. package/dist/retriableDocumentStorageService.js +8 -6
  78. package/dist/retriableDocumentStorageService.js.map +1 -1
  79. package/dist/serializedStateManager.d.ts +44 -0
  80. package/dist/serializedStateManager.d.ts.map +1 -0
  81. package/dist/serializedStateManager.js +149 -0
  82. package/dist/serializedStateManager.js.map +1 -0
  83. package/dist/tsdoc-metadata.json +1 -1
  84. package/dist/utils.d.ts +16 -11
  85. package/dist/utils.d.ts.map +1 -1
  86. package/dist/utils.js +107 -32
  87. package/dist/utils.js.map +1 -1
  88. package/lib/attachment.d.ts +115 -0
  89. package/lib/attachment.d.ts.map +1 -0
  90. package/lib/attachment.js +79 -0
  91. package/lib/attachment.js.map +1 -0
  92. package/lib/{audience.d.mts → audience.d.ts} +14 -5
  93. package/lib/audience.d.ts.map +1 -0
  94. package/lib/{audience.mjs → audience.js} +14 -4
  95. package/lib/audience.js.map +1 -0
  96. package/lib/{catchUpMonitor.d.mts → catchUpMonitor.d.ts} +1 -1
  97. package/lib/catchUpMonitor.d.ts.map +1 -0
  98. package/lib/{catchUpMonitor.mjs → catchUpMonitor.js} +1 -1
  99. package/lib/catchUpMonitor.js.map +1 -0
  100. package/lib/{connectionManager.d.mts → connectionManager.d.ts} +4 -4
  101. package/lib/connectionManager.d.ts.map +1 -0
  102. package/lib/{connectionManager.mjs → connectionManager.js} +7 -10
  103. package/lib/connectionManager.js.map +1 -0
  104. package/lib/{connectionState.d.mts → connectionState.d.ts} +2 -1
  105. package/lib/connectionState.d.ts.map +1 -0
  106. package/lib/{connectionState.mjs → connectionState.js} +2 -1
  107. package/lib/connectionState.js.map +1 -0
  108. package/lib/{connectionStateHandler.d.mts → connectionStateHandler.d.ts} +8 -8
  109. package/lib/connectionStateHandler.d.ts.map +1 -0
  110. package/lib/{connectionStateHandler.mjs → connectionStateHandler.js} +3 -3
  111. package/lib/connectionStateHandler.js.map +1 -0
  112. package/lib/{container-loader-alpha.d.mts → container-loader-alpha.d.ts} +2 -1
  113. package/lib/{container-loader-beta.d.mts → container-loader-beta.d.ts} +3 -0
  114. package/lib/{container-loader-public.d.mts → container-loader-public.d.ts} +3 -0
  115. package/lib/{container-loader-untrimmed.d.mts → container-loader-untrimmed.d.ts} +5 -5
  116. package/lib/{container.d.mts → container.d.ts} +30 -28
  117. package/lib/container.d.ts.map +1 -0
  118. package/lib/{container.mjs → container.js} +178 -247
  119. package/lib/container.js.map +1 -0
  120. package/lib/{containerContext.d.mts → containerContext.d.ts} +4 -3
  121. package/lib/containerContext.d.ts.map +1 -0
  122. package/lib/{containerContext.mjs → containerContext.js} +3 -2
  123. package/lib/containerContext.js.map +1 -0
  124. package/lib/{containerStorageAdapter.d.mts → containerStorageAdapter.d.ts} +6 -7
  125. package/lib/containerStorageAdapter.d.ts.map +1 -0
  126. package/lib/{containerStorageAdapter.mjs → containerStorageAdapter.js} +13 -20
  127. package/lib/containerStorageAdapter.js.map +1 -0
  128. package/lib/{contracts.d.mts → contracts.d.ts} +4 -4
  129. package/lib/contracts.d.ts.map +1 -0
  130. package/lib/{contracts.mjs → contracts.js} +1 -1
  131. package/lib/contracts.js.map +1 -0
  132. package/lib/{debugLogger.d.mts → debugLogger.d.ts} +1 -1
  133. package/lib/debugLogger.d.ts.map +1 -0
  134. package/lib/{debugLogger.mjs → debugLogger.js} +2 -1
  135. package/lib/debugLogger.js.map +1 -0
  136. package/lib/{deltaManager.d.mts → deltaManager.d.ts} +6 -6
  137. package/lib/deltaManager.d.ts.map +1 -0
  138. package/lib/{deltaManager.mjs → deltaManager.js} +4 -4
  139. package/lib/deltaManager.js.map +1 -0
  140. package/lib/{deltaQueue.d.mts → deltaQueue.d.ts} +1 -1
  141. package/lib/deltaQueue.d.ts.map +1 -0
  142. package/lib/{deltaQueue.mjs → deltaQueue.js} +1 -1
  143. package/lib/deltaQueue.js.map +1 -0
  144. package/lib/{disposal.d.mts → disposal.d.ts} +1 -1
  145. package/lib/disposal.d.ts.map +1 -0
  146. package/lib/{disposal.mjs → disposal.js} +1 -1
  147. package/lib/disposal.js.map +1 -0
  148. package/lib/{error.d.mts → error.d.ts} +1 -1
  149. package/lib/error.d.ts.map +1 -0
  150. package/lib/{error.mjs → error.js} +1 -1
  151. package/lib/error.js.map +1 -0
  152. package/lib/{index.d.mts → index.d.ts} +7 -7
  153. package/lib/index.d.ts.map +1 -0
  154. package/lib/index.js +10 -0
  155. package/lib/index.js.map +1 -0
  156. package/lib/{loader.d.mts → loader.d.ts} +4 -4
  157. package/lib/loader.d.ts.map +1 -0
  158. package/lib/{loader.mjs → loader.js} +7 -11
  159. package/lib/loader.js.map +1 -0
  160. package/lib/location-redirection-utilities/{index.mjs → index.d.ts} +2 -2
  161. package/lib/location-redirection-utilities/index.d.ts.map +1 -0
  162. package/lib/location-redirection-utilities/{index.d.mts → index.js} +2 -2
  163. package/lib/location-redirection-utilities/index.js.map +1 -0
  164. package/lib/location-redirection-utilities/{resolveWithLocationRedirection.d.mts → resolveWithLocationRedirection.d.ts} +1 -1
  165. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  166. package/lib/location-redirection-utilities/{resolveWithLocationRedirection.mjs → resolveWithLocationRedirection.js} +1 -1
  167. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  168. package/lib/{noopHeuristic.d.mts → noopHeuristic.d.ts} +1 -1
  169. package/lib/noopHeuristic.d.ts.map +1 -0
  170. package/lib/{noopHeuristic.mjs → noopHeuristic.js} +1 -1
  171. package/lib/noopHeuristic.js.map +1 -0
  172. package/lib/{packageVersion.d.mts → packageVersion.d.ts} +2 -2
  173. package/lib/packageVersion.d.ts.map +1 -0
  174. package/lib/{packageVersion.mjs → packageVersion.js} +2 -2
  175. package/lib/packageVersion.js.map +1 -0
  176. package/lib/{protocol.d.mts → protocol.d.ts} +1 -1
  177. package/lib/protocol.d.ts.map +1 -0
  178. package/lib/{protocol.mjs → protocol.js} +1 -1
  179. package/lib/protocol.js.map +1 -0
  180. package/lib/{protocolTreeDocumentStorageService.d.mts → protocolTreeDocumentStorageService.d.ts} +2 -2
  181. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -0
  182. package/lib/{protocolTreeDocumentStorageService.mjs → protocolTreeDocumentStorageService.js} +2 -4
  183. package/lib/protocolTreeDocumentStorageService.js.map +1 -0
  184. package/lib/{quorum.d.mts → quorum.d.ts} +5 -1
  185. package/lib/quorum.d.ts.map +1 -0
  186. package/lib/{quorum.mjs → quorum.js} +1 -1
  187. package/lib/quorum.js.map +1 -0
  188. package/lib/{retriableDocumentStorageService.d.mts → retriableDocumentStorageService.d.ts} +3 -3
  189. package/lib/retriableDocumentStorageService.d.ts.map +1 -0
  190. package/lib/{retriableDocumentStorageService.mjs → retriableDocumentStorageService.js} +10 -8
  191. package/lib/retriableDocumentStorageService.js.map +1 -0
  192. package/lib/serializedStateManager.d.ts +44 -0
  193. package/lib/serializedStateManager.d.ts.map +1 -0
  194. package/lib/serializedStateManager.js +145 -0
  195. package/lib/serializedStateManager.js.map +1 -0
  196. package/lib/test/attachment.spec.js +380 -0
  197. package/lib/test/attachment.spec.js.map +1 -0
  198. package/lib/test/catchUpMonitor.spec.js +88 -0
  199. package/lib/test/catchUpMonitor.spec.js.map +1 -0
  200. package/lib/test/connectionManager.spec.js +201 -0
  201. package/lib/test/connectionManager.spec.js.map +1 -0
  202. package/lib/test/connectionStateHandler.spec.js +555 -0
  203. package/lib/test/connectionStateHandler.spec.js.map +1 -0
  204. package/lib/test/container.spec.js +64 -0
  205. package/lib/test/container.spec.js.map +1 -0
  206. package/lib/test/deltaManager.spec.js +405 -0
  207. package/lib/test/deltaManager.spec.js.map +1 -0
  208. package/lib/test/loader.spec.js +212 -0
  209. package/lib/test/loader.spec.js.map +1 -0
  210. package/lib/test/locationRedirectionTests.spec.js +44 -0
  211. package/lib/test/locationRedirectionTests.spec.js.map +1 -0
  212. package/lib/test/serializedStateManager.spec.js +148 -0
  213. package/lib/test/serializedStateManager.spec.js.map +1 -0
  214. package/lib/test/snapshotConversionTest.spec.js +79 -0
  215. package/lib/test/snapshotConversionTest.spec.js.map +1 -0
  216. package/lib/test/types/validateContainerLoaderPrevious.generated.js +38 -0
  217. package/lib/test/types/validateContainerLoaderPrevious.generated.js.map +1 -0
  218. package/lib/test/utils.spec.js +31 -0
  219. package/lib/test/utils.spec.js.map +1 -0
  220. package/lib/{utils.d.mts → utils.d.ts} +17 -12
  221. package/lib/utils.d.ts.map +1 -0
  222. package/lib/utils.js +206 -0
  223. package/lib/utils.js.map +1 -0
  224. package/package.json +61 -63
  225. package/src/attachment.ts +222 -0
  226. package/src/audience.ts +9 -3
  227. package/src/connectionManager.ts +9 -11
  228. package/src/connectionState.ts +1 -0
  229. package/src/connectionStateHandler.ts +8 -7
  230. package/src/container.ts +296 -323
  231. package/src/containerContext.ts +2 -1
  232. package/src/containerStorageAdapter.ts +21 -26
  233. package/src/contracts.ts +3 -3
  234. package/src/debugLogger.ts +2 -2
  235. package/src/deltaManager.ts +8 -8
  236. package/src/error.ts +2 -2
  237. package/src/index.ts +6 -6
  238. package/src/loader.ts +9 -13
  239. package/src/location-redirection-utilities/index.ts +1 -1
  240. package/src/packageVersion.ts +1 -1
  241. package/src/protocolTreeDocumentStorageService.ts +1 -3
  242. package/src/retriableDocumentStorageService.ts +18 -8
  243. package/src/serializedStateManager.ts +217 -0
  244. package/src/utils.ts +140 -43
  245. package/tsconfig.cjs.json +7 -0
  246. package/tsconfig.json +2 -5
  247. package/lib/audience.d.mts.map +0 -1
  248. package/lib/audience.mjs.map +0 -1
  249. package/lib/catchUpMonitor.d.mts.map +0 -1
  250. package/lib/catchUpMonitor.mjs.map +0 -1
  251. package/lib/connectionManager.d.mts.map +0 -1
  252. package/lib/connectionManager.mjs.map +0 -1
  253. package/lib/connectionState.d.mts.map +0 -1
  254. package/lib/connectionState.mjs.map +0 -1
  255. package/lib/connectionStateHandler.d.mts.map +0 -1
  256. package/lib/connectionStateHandler.mjs.map +0 -1
  257. package/lib/container.d.mts.map +0 -1
  258. package/lib/container.mjs.map +0 -1
  259. package/lib/containerContext.d.mts.map +0 -1
  260. package/lib/containerContext.mjs.map +0 -1
  261. package/lib/containerStorageAdapter.d.mts.map +0 -1
  262. package/lib/containerStorageAdapter.mjs.map +0 -1
  263. package/lib/contracts.d.mts.map +0 -1
  264. package/lib/contracts.mjs.map +0 -1
  265. package/lib/debugLogger.d.mts.map +0 -1
  266. package/lib/debugLogger.mjs.map +0 -1
  267. package/lib/deltaManager.d.mts.map +0 -1
  268. package/lib/deltaManager.mjs.map +0 -1
  269. package/lib/deltaQueue.d.mts.map +0 -1
  270. package/lib/deltaQueue.mjs.map +0 -1
  271. package/lib/disposal.d.mts.map +0 -1
  272. package/lib/disposal.mjs.map +0 -1
  273. package/lib/error.d.mts.map +0 -1
  274. package/lib/error.mjs.map +0 -1
  275. package/lib/index.d.mts.map +0 -1
  276. package/lib/index.mjs +0 -10
  277. package/lib/index.mjs.map +0 -1
  278. package/lib/loader.d.mts.map +0 -1
  279. package/lib/loader.mjs.map +0 -1
  280. package/lib/location-redirection-utilities/index.d.mts.map +0 -1
  281. package/lib/location-redirection-utilities/index.mjs.map +0 -1
  282. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.mts.map +0 -1
  283. package/lib/location-redirection-utilities/resolveWithLocationRedirection.mjs.map +0 -1
  284. package/lib/noopHeuristic.d.mts.map +0 -1
  285. package/lib/noopHeuristic.mjs.map +0 -1
  286. package/lib/packageVersion.d.mts.map +0 -1
  287. package/lib/packageVersion.mjs.map +0 -1
  288. package/lib/protocol.d.mts.map +0 -1
  289. package/lib/protocol.mjs.map +0 -1
  290. package/lib/protocolTreeDocumentStorageService.d.mts.map +0 -1
  291. package/lib/protocolTreeDocumentStorageService.mjs.map +0 -1
  292. package/lib/quorum.d.mts.map +0 -1
  293. package/lib/quorum.mjs.map +0 -1
  294. package/lib/retriableDocumentStorageService.d.mts.map +0 -1
  295. package/lib/retriableDocumentStorageService.mjs.map +0 -1
  296. package/lib/utils.d.mts.map +0 -1
  297. package/lib/utils.mjs +0 -133
  298. package/lib/utils.mjs.map +0 -1
package/src/container.ts CHANGED
@@ -4,15 +4,14 @@
4
4
  */
5
5
 
6
6
  import { v4 as uuid } from "uuid";
7
- import { assert, unreachableCase } from "@fluidframework/core-utils";
7
+ import { assert, unreachableCase, isPromiseLike } from "@fluidframework/core-utils";
8
8
  import { TypedEventEmitter, performance } from "@fluid-internal/client-utils";
9
9
  import {
10
10
  IEvent,
11
- ITelemetryProperties,
12
- TelemetryEventCategory,
13
- IRequest,
11
+ ITelemetryBaseProperties,
14
12
  FluidObject,
15
13
  LogLevel,
14
+ IRequest,
16
15
  } from "@fluidframework/core-interfaces";
17
16
  import {
18
17
  AttachState,
@@ -41,6 +40,7 @@ import {
41
40
  IDocumentServiceFactory,
42
41
  IDocumentStorageService,
43
42
  IResolvedUrl,
43
+ ISnapshot,
44
44
  IThrottlingWarning,
45
45
  IUrlResolver,
46
46
  } from "@fluidframework/driver-definitions";
@@ -48,9 +48,10 @@ import {
48
48
  readAndParse,
49
49
  OnlineStatus,
50
50
  isOnline,
51
- runWithRetry,
52
51
  isCombinedAppAndProtocolSummary,
53
52
  MessageType2,
53
+ isInstanceOfISnapshot,
54
+ runWithRetry,
54
55
  } from "@fluidframework/driver-utils";
55
56
  import { IQuorumSnapshot } from "@fluidframework/protocol-base";
56
57
  import {
@@ -86,42 +87,45 @@ import {
86
87
  formatTick,
87
88
  GenericError,
88
89
  UsageError,
90
+ IFluidErrorBase,
91
+ type TelemetryEventCategory,
89
92
  } from "@fluidframework/telemetry-utils";
90
93
  import structuredClone from "@ungap/structured-clone";
91
- import { Audience } from "./audience";
92
- import { ContainerContext } from "./containerContext";
94
+ import { Audience } from "./audience.js";
95
+ import { ContainerContext } from "./containerContext.js";
93
96
  import {
94
97
  ReconnectMode,
95
98
  IConnectionManagerFactoryArgs,
96
99
  getPackageName,
97
100
  IConnectionDetailsInternal,
98
101
  IConnectionStateChangeReason,
99
- } from "./contracts";
100
- import { DeltaManager, IConnectionArgs } from "./deltaManager";
101
- import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
102
- import { pkgVersion } from "./packageVersion";
103
- import {
104
- ContainerStorageAdapter,
105
- getBlobContentsFromTree,
106
- getBlobContentsFromTreeWithBlobContents,
107
- ISerializableBlobContents,
108
- } from "./containerStorageAdapter";
109
- import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
102
+ } from "./contracts.js";
103
+ import { DeltaManager, IConnectionArgs } from "./deltaManager.js";
104
+ import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader.js";
105
+ import { pkgVersion } from "./packageVersion.js";
106
+ import { ContainerStorageAdapter, ISerializableBlobContents } from "./containerStorageAdapter.js";
107
+ import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler.js";
110
108
  import {
109
+ ISnapshotTreeWithBlobContents,
111
110
  combineAppAndProtocolSummary,
112
111
  getProtocolSnapshotTree,
113
- getSnapshotTreeFromSerializedContainer,
114
- } from "./utils";
115
- import { initQuorumValuesFromCodeDetails } from "./quorum";
116
- import { NoopHeuristic } from "./noopHeuristic";
117
- import { ConnectionManager } from "./connectionManager";
118
- import { ConnectionState } from "./connectionState";
112
+ getSnapshotTreeAndBlobsFromSerializedContainer,
113
+ combineSnapshotTreeAndSnapshotBlobs,
114
+ getDetachedContainerStateFromSerializedContainer,
115
+ runSingle,
116
+ } from "./utils.js";
117
+ import { initQuorumValuesFromCodeDetails } from "./quorum.js";
118
+ import { NoopHeuristic } from "./noopHeuristic.js";
119
+ import { ConnectionManager } from "./connectionManager.js";
120
+ import { ConnectionState } from "./connectionState.js";
119
121
  import {
120
122
  IProtocolHandler,
121
123
  ProtocolHandler,
122
124
  ProtocolHandlerBuilder,
123
125
  protocolHandlerShouldProcessSignal,
124
- } from "./protocol";
126
+ } from "./protocol.js";
127
+ import { AttachProcessProps, AttachmentData, runRetriableAttachProcess } from "./attachment.js";
128
+ import { SerializedStateManager } from "./serializedStateManager.js";
125
129
 
126
130
  const detachedContainerRefSeqNumber = 0;
127
131
 
@@ -130,8 +134,6 @@ const savedContainerEvent = "saved";
130
134
 
131
135
  const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
132
136
 
133
- const hasBlobsSummaryTree = ".hasAttachmentBlobs";
134
-
135
137
  /**
136
138
  * @internal
137
139
  */
@@ -313,9 +315,7 @@ export async function waitContainerToCatchUp(container: IContainer) {
313
315
  });
314
316
  }
315
317
 
316
- const getCodeProposal =
317
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
318
- (quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
318
+ const getCodeProposal = (quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
319
319
 
320
320
  /**
321
321
  * Helper function to report to telemetry cases where operation takes longer than expected (200ms)
@@ -326,7 +326,7 @@ const getCodeProposal =
326
326
  export async function ReportIfTooLong(
327
327
  logger: ITelemetryLoggerExt,
328
328
  eventName: string,
329
- action: () => Promise<ITelemetryProperties>,
329
+ action: () => Promise<ITelemetryBaseProperties>,
330
330
  ) {
331
331
  const event = PerformanceEvent.start(logger, { eventName });
332
332
  const props = await action();
@@ -341,6 +341,7 @@ export async function ReportIfTooLong(
341
341
  * @internal
342
342
  */
343
343
  export interface IPendingContainerState {
344
+ attached: true;
344
345
  pendingRuntimeState: unknown;
345
346
  /**
346
347
  * Snapshot from which container initially loaded.
@@ -361,6 +362,19 @@ export interface IPendingContainerState {
361
362
  clientId?: string;
362
363
  }
363
364
 
365
+ /**
366
+ * State saved by a container in detached state, to be used to load a new instance
367
+ * of the container to the same state (rehydrate)
368
+ * @internal
369
+ */
370
+ export interface IPendingDetachedContainerState {
371
+ attached: false;
372
+ baseSnapshot: ISnapshotTree;
373
+ snapshotBlobs: ISerializableBlobContents;
374
+ hasAttachmentBlobs: boolean;
375
+ pendingRuntimeState?: unknown;
376
+ }
377
+
364
378
  const summarizerClientType = "summarizer";
365
379
 
366
380
  interface IContainerLifecycleEvents extends IEvent {
@@ -374,7 +388,6 @@ export class Container
374
388
  {
375
389
  /**
376
390
  * Load an existing container.
377
- * @internal
378
391
  */
379
392
  public static async load(
380
393
  loadProps: IContainerLoadProps,
@@ -472,12 +485,9 @@ export class Container
472
485
  container.mc.logger,
473
486
  { eventName: "RehydrateDetachedFromSnapshot" },
474
487
  async (_event) => {
475
- const deserializedSummary = JSON.parse(snapshot);
476
- if (!isCombinedAppAndProtocolSummary(deserializedSummary, hasBlobsSummaryTree)) {
477
- throw new UsageError("Cannot rehydrate detached container. Incorrect format");
478
- }
479
-
480
- await container.rehydrateDetachedFromSnapshot(deserializedSummary);
488
+ const detachedContainerState: IPendingDetachedContainerState =
489
+ getDetachedContainerStateFromSerializedContainer(snapshot);
490
+ await container.rehydrateDetachedFromSnapshot(detachedContainerState);
481
491
  return container;
482
492
  },
483
493
  { start: true, end: true, cancel: "generic" },
@@ -502,7 +512,6 @@ export class Container
502
512
 
503
513
  /**
504
514
  * Used by the RelativeLoader to spawn a new Container for the same document. Used to create the summarizing client.
505
- * @internal
506
515
  */
507
516
  public readonly clone: (
508
517
  loadProps: IContainerLoadProps,
@@ -552,8 +561,6 @@ export class Container
552
561
  return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
553
562
  }
554
563
 
555
- private _attachState = AttachState.Detached;
556
-
557
564
  private readonly storageAdapter: ContainerStorageAdapter;
558
565
 
559
566
  private readonly _deltaManager: DeltaManager<ConnectionManager>;
@@ -579,17 +586,16 @@ export class Container
579
586
  private firstConnection = true;
580
587
  private readonly connectionTransitionTimes: number[] = [];
581
588
  private _loadedFromVersion: IVersion | undefined;
582
- private attachStarted = false;
583
589
  private _dirtyContainer = false;
584
- private readonly savedOps: ISequencedDocumentMessage[] = [];
585
- private baseSnapshot?: ISnapshotTree;
586
- private baseSnapshotBlobs?: ISerializableBlobContents;
590
+ private attachmentData: AttachmentData = { state: AttachState.Detached };
591
+ private readonly serializedStateManager: SerializedStateManager;
587
592
  private readonly _containerId: string;
588
593
 
589
594
  private lastVisible: number | undefined;
590
595
  private readonly visibilityEventHandler: (() => void) | undefined;
591
596
  private readonly connectionStateHandler: IConnectionStateHandler;
592
597
  private readonly clientsWhoShouldHaveLeft = new Set<string>();
598
+ private _containerMetadata: Readonly<Record<string, string>> = {};
593
599
 
594
600
  private setAutoReconnectTime = performance.now();
595
601
 
@@ -618,6 +624,10 @@ export class Container
618
624
  return this._deltaManager.readOnlyInfo;
619
625
  }
620
626
 
627
+ public get containerMetadata(): Record<string, string> {
628
+ return this._containerMetadata;
629
+ }
630
+
621
631
  /**
622
632
  * Sends signal to runtime (and data stores) to be read-only.
623
633
  * Hosts may have read only views, indicating to data stores that no edits are allowed.
@@ -661,12 +671,8 @@ export class Container
661
671
  return this._clientId;
662
672
  }
663
673
 
664
- private get offlineLoadEnabled(): boolean {
665
- const enabled =
666
- this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
667
- this.options?.enableOfflineLoad === true;
668
- // summarizer will not have any pending state we want to save
669
- return enabled && this.deltaManager.clientDetails.capabilities.interactive;
674
+ private get isInteractiveClient(): boolean {
675
+ return this.deltaManager.clientDetails.capabilities.interactive;
670
676
  }
671
677
 
672
678
  /**
@@ -735,9 +741,6 @@ export class Container
735
741
 
736
742
  private readonly _lifecycleEvents = new TypedEventEmitter<IContainerLifecycleEvents>();
737
743
 
738
- /**
739
- * @internal
740
- */
741
744
  constructor(
742
745
  createProps: IContainerCreateProps,
743
746
  loadProps?: Pick<IContainerLoadProps, "pendingLocalState">,
@@ -810,7 +813,7 @@ export class Container
810
813
 
811
814
  this.client = Container.setupClient(
812
815
  this._containerId,
813
- this.options,
816
+ options.client,
814
817
  this.clientDetailsOverride,
815
818
  );
816
819
 
@@ -830,7 +833,7 @@ export class Container
830
833
  clientType, // Differentiating summarizer container from main container
831
834
  containerId: this._containerId,
832
835
  docId: () => this.resolvedUrl?.id,
833
- containerAttachState: () => this._attachState,
836
+ containerAttachState: () => this.attachState,
834
837
  containerLifecycleState: () => this._lifecycleState,
835
838
  containerConnectionState: () => ConnectionState[this.connectionState],
836
839
  serializedContainer: pendingLocalState !== undefined,
@@ -887,7 +890,7 @@ export class Container
887
890
  logConnectionIssue: (
888
891
  eventName: string,
889
892
  category: TelemetryEventCategory,
890
- details?: ITelemetryProperties,
893
+ details?: ITelemetryBaseProperties,
891
894
  ) => {
892
895
  const mode = this.connectionMode;
893
896
  // We get here when socket does not receive any ops on "write" connection, including
@@ -953,6 +956,17 @@ export class Container
953
956
  forceEnableSummarizeProtocolTree,
954
957
  );
955
958
 
959
+ const offlineLoadEnabled =
960
+ (this.isInteractiveClient &&
961
+ this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) ??
962
+ options.enableOfflineLoad === true;
963
+ this.serializedStateManager = new SerializedStateManager(
964
+ pendingLocalState,
965
+ this.subLogger,
966
+ this.storageAdapter,
967
+ offlineLoadEnabled,
968
+ );
969
+
956
970
  const isDomAvailable =
957
971
  typeof document === "object" &&
958
972
  document !== null &&
@@ -1031,6 +1045,11 @@ export class Container
1031
1045
 
1032
1046
  this._lifecycleState = "closing";
1033
1047
 
1048
+ // Back-compat for Old driver
1049
+ if (this.service?.off !== undefined) {
1050
+ this.service?.off("metadataUpdate", this.metadataUpdateHandler);
1051
+ }
1052
+
1034
1053
  this._protocolHandler?.close();
1035
1054
 
1036
1055
  this.connectionStateHandler.dispose();
@@ -1128,202 +1147,175 @@ export class Container
1128
1147
  }
1129
1148
 
1130
1149
  private async getPendingLocalStateCore(props: IGetPendingLocalStateProps) {
1131
- return PerformanceEvent.timedExecAsync(
1132
- this.mc.logger,
1133
- {
1134
- eventName: "getPendingLocalState",
1135
- notifyImminentClosure: props.notifyImminentClosure,
1136
- savedOpsSize: this.savedOps.length,
1137
- clientId: this.clientId,
1138
- },
1139
- async () => {
1140
- if (!this.offlineLoadEnabled) {
1141
- throw new UsageError(
1142
- "Can't get pending local state unless offline load is enabled",
1143
- );
1144
- }
1145
- if (this.closed || this._disposed) {
1146
- throw new UsageError(
1147
- "Pending state cannot be retried if the container is closed or disposed",
1148
- );
1149
- }
1150
- assert(
1151
- this.attachState === AttachState.Attached,
1152
- 0x0d1 /* "Container should be attached before close" */,
1153
- );
1154
- assert(
1155
- this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
1156
- 0x0d2 /* "resolved url should be valid Fluid url" */,
1157
- );
1158
- assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
1159
- assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
1160
- const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
1161
- const pendingState: IPendingContainerState = {
1162
- pendingRuntimeState,
1163
- baseSnapshot: this.baseSnapshot,
1164
- snapshotBlobs: this.baseSnapshotBlobs,
1165
- savedOps: this.savedOps,
1166
- url: this.resolvedUrl.url,
1167
- // no need to save this if there is no pending runtime state
1168
- clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
1169
- };
1170
-
1171
- return JSON.stringify(pendingState);
1172
- },
1150
+ if (this.closed || this._disposed) {
1151
+ throw new UsageError(
1152
+ "Pending state cannot be retried if the container is closed or disposed",
1153
+ );
1154
+ }
1155
+ assert(
1156
+ this.attachmentData.state === AttachState.Attached,
1157
+ 0x0d1 /* "Container should be attached before close" */,
1158
+ );
1159
+ assert(
1160
+ this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
1161
+ 0x0d2 /* "resolved url should be valid Fluid url" */,
1162
+ );
1163
+ const pendingState = await this.serializedStateManager.getPendingLocalStateCore(
1164
+ props,
1165
+ this.clientId,
1166
+ this.runtime,
1167
+ this.resolvedUrl,
1173
1168
  );
1169
+ return pendingState;
1174
1170
  }
1175
1171
 
1176
1172
  public get attachState(): AttachState {
1177
- return this._attachState;
1173
+ return this.attachmentData.state;
1178
1174
  }
1179
1175
 
1180
1176
  public serialize(): string {
1181
- assert(
1182
- this.attachState === AttachState.Detached,
1183
- 0x0d3 /* "Should only be called in detached container" */,
1184
- );
1185
-
1186
- const appSummary: ISummaryTree = this.runtime.createSummary();
1187
- const protocolSummary = this.captureProtocolSummary();
1188
- const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1189
-
1190
- if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
1191
- combinedSummary.tree[hasBlobsSummaryTree] = {
1192
- type: SummaryType.Blob,
1193
- content: "true",
1194
- };
1177
+ if (this.attachmentData.state === AttachState.Attached || this.closed) {
1178
+ throw new UsageError("Container must not be attached or closed.");
1195
1179
  }
1196
- return JSON.stringify(combinedSummary);
1197
- }
1198
1180
 
1199
- public async attach(
1200
- request: IRequest,
1201
- attachProps?: { deltaConnection?: "none" | "delayed" },
1202
- ): Promise<void> {
1203
- await PerformanceEvent.timedExecAsync(
1204
- this.mc.logger,
1205
- { eventName: "Attach" },
1206
- async () => {
1207
- if (this._lifecycleState !== "loaded") {
1208
- // pre-0.58 error message: containerNotValidForAttach
1209
- throw new UsageError(
1210
- `The Container is not in a valid state for attach [${this._lifecycleState}]`,
1211
- );
1212
- }
1181
+ const attachingData =
1182
+ this.attachmentData.state === AttachState.Attaching ? this.attachmentData : undefined;
1213
1183
 
1214
- // If container is already attached or attach is in progress, throw an error.
1215
- assert(
1216
- this._attachState === AttachState.Detached && !this.attachStarted,
1217
- 0x205 /* "attach() called more than once" */,
1218
- );
1219
- this.attachStarted = true;
1184
+ const combinedSummary =
1185
+ attachingData?.summary ??
1186
+ combineAppAndProtocolSummary(
1187
+ this.runtime.createSummary(),
1188
+ this.captureProtocolSummary(),
1189
+ );
1220
1190
 
1221
- // If attachment blobs were uploaded in detached state we will go through a different attach flow
1222
- const hasAttachmentBlobs =
1223
- this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
1191
+ const { tree: snapshot, blobs } =
1192
+ getSnapshotTreeAndBlobsFromSerializedContainer(combinedSummary);
1224
1193
 
1225
- try {
1226
- assert(
1227
- this.deltaManager.inbound.length === 0,
1228
- 0x0d6 /* "Inbound queue should be empty when attaching" */,
1229
- );
1194
+ const pendingRuntimeState =
1195
+ attachingData !== undefined ? this.runtime.getPendingLocalState() : undefined;
1196
+ assert(!isPromiseLike(pendingRuntimeState), 0x8e3 /* should not be a promise */);
1230
1197
 
1231
- let summary: ISummaryTree;
1232
- if (!hasAttachmentBlobs) {
1233
- // Get the document state post attach - possibly can just call attach but we need to change the
1234
- // semantics around what the attach means as far as async code goes.
1235
- const appSummary: ISummaryTree = this.runtime.createSummary();
1236
- const protocolSummary = this.captureProtocolSummary();
1237
- summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1238
-
1239
- // Set the state as attaching as we are starting the process of attaching container.
1240
- // This should be fired after taking the summary because it is the place where we are
1241
- // starting to attach the container to storage.
1242
- // Also, this should only be fired in detached container.
1243
- this._attachState = AttachState.Attaching;
1244
- this.runtime.setAttachState(AttachState.Attaching);
1245
- this.emit("attaching");
1246
- if (this.offlineLoadEnabled) {
1247
- const snapshot = getSnapshotTreeFromSerializedContainer(summary);
1248
- this.baseSnapshot = snapshot;
1249
- this.baseSnapshotBlobs =
1250
- getBlobContentsFromTreeWithBlobContents(snapshot);
1251
- }
1252
- }
1253
-
1254
- // Actually go and create the resolved document
1255
- if (this.service === undefined) {
1256
- const createNewResolvedUrl = await this.urlResolver.resolve(request);
1257
- assert(
1258
- this.client.details.type !== summarizerClientType &&
1259
- createNewResolvedUrl !== undefined,
1260
- 0x2c4 /* "client should not be summarizer before container is created" */,
1261
- );
1262
- this.service = await runWithRetry(
1263
- async () =>
1264
- this.serviceFactory.createContainer(
1265
- summary,
1266
- createNewResolvedUrl,
1267
- this.subLogger,
1268
- false, // clientIsSummarizer
1269
- ),
1270
- "containerAttach",
1271
- this.mc.logger,
1272
- {
1273
- cancel: this._deltaManager.closeAbortController.signal,
1274
- }, // progress
1198
+ const detachedContainerState: IPendingDetachedContainerState = {
1199
+ attached: false,
1200
+ baseSnapshot: snapshot,
1201
+ snapshotBlobs: blobs,
1202
+ pendingRuntimeState,
1203
+ hasAttachmentBlobs: !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1204
+ };
1205
+ return JSON.stringify(detachedContainerState);
1206
+ }
1207
+
1208
+ public readonly attach = runSingle(
1209
+ async (
1210
+ request: IRequest,
1211
+ attachProps?: { deltaConnection?: "none" | "delayed" },
1212
+ ): Promise<void> => {
1213
+ await PerformanceEvent.timedExecAsync(
1214
+ this.mc.logger,
1215
+ { eventName: "Attach" },
1216
+ async () => {
1217
+ if (
1218
+ this._lifecycleState !== "loaded" ||
1219
+ this.attachmentData.state === AttachState.Attached
1220
+ ) {
1221
+ // pre-0.58 error message: containerNotValidForAttach
1222
+ throw new UsageError(
1223
+ `The Container is not in a valid state for attach [${this._lifecycleState}] and [${this.attachState}]`,
1275
1224
  );
1276
1225
  }
1277
- this.storageAdapter.connectToService(this.service);
1278
1226
 
1279
- if (hasAttachmentBlobs) {
1280
- // upload blobs to storage
1281
- assert(
1282
- !!this.detachedBlobStorage,
1283
- 0x24e /* "assertion for type narrowing" */,
1284
- );
1227
+ const normalizeErrorAndClose = (error: unknown): IFluidErrorBase => {
1228
+ const newError = normalizeError(error);
1229
+ this.close(newError);
1230
+ // add resolved URL on error object so that host has the ability to find this document and delete it
1231
+ newError.addTelemetryProperties({
1232
+ resolvedUrl: this.service?.resolvedUrl?.url,
1233
+ });
1234
+ return newError;
1235
+ };
1285
1236
 
1286
- // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
1287
- // support blob handles that only know about the local IDs
1288
- const redirectTable = new Map<string, string>();
1289
- // if new blobs are added while uploading, upload them too
1290
- while (redirectTable.size < this.detachedBlobStorage.size) {
1291
- const newIds = this.detachedBlobStorage
1292
- .getBlobIds()
1293
- .filter((id) => !redirectTable.has(id));
1294
- for (const id of newIds) {
1295
- const blob = await this.detachedBlobStorage.readBlob(id);
1296
- const response = await this.storageAdapter.createBlob(blob);
1297
- redirectTable.set(id, response.id);
1237
+ const setAttachmentData: AttachProcessProps["setAttachmentData"] = (
1238
+ attachmentData,
1239
+ ) => {
1240
+ const previousState = this.attachmentData.state;
1241
+ this.attachmentData = attachmentData;
1242
+ const state = this.attachmentData.state;
1243
+ if (state !== previousState && state !== AttachState.Detached) {
1244
+ try {
1245
+ this.runtime.setAttachState(state);
1246
+ this.emit(state.toLocaleLowerCase());
1247
+ } catch (error) {
1248
+ throw normalizeErrorAndClose(error);
1298
1249
  }
1299
1250
  }
1251
+ };
1300
1252
 
1301
- // take summary and upload
1302
- const appSummary: ISummaryTree = this.runtime.createSummary(redirectTable);
1303
- const protocolSummary = this.captureProtocolSummary();
1304
- summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1305
-
1306
- this._attachState = AttachState.Attaching;
1307
- this.runtime.setAttachState(AttachState.Attaching);
1308
- this.emit("attaching");
1309
- if (this.offlineLoadEnabled) {
1310
- const snapshot = getSnapshotTreeFromSerializedContainer(summary);
1311
- this.baseSnapshot = snapshot;
1312
- this.baseSnapshotBlobs =
1313
- getBlobContentsFromTreeWithBlobContents(snapshot);
1253
+ const createAttachmentSummary: AttachProcessProps["createAttachmentSummary"] = (
1254
+ redirectTable?: Map<string, string>,
1255
+ ) => {
1256
+ try {
1257
+ assert(
1258
+ this.deltaManager.inbound.length === 0,
1259
+ 0x0d6 /* "Inbound queue should be empty when attaching" */,
1260
+ );
1261
+ return combineAppAndProtocolSummary(
1262
+ this.runtime.createSummary(redirectTable),
1263
+ this.captureProtocolSummary(),
1264
+ );
1265
+ } catch (error) {
1266
+ throw normalizeErrorAndClose(error);
1314
1267
  }
1268
+ };
1269
+
1270
+ const createOrGetStorageService: AttachProcessProps["createOrGetStorageService"] =
1271
+ async (summary) => {
1272
+ // Actually go and create the resolved document
1273
+ if (this.service === undefined) {
1274
+ const createNewResolvedUrl =
1275
+ await this.urlResolver.resolve(request);
1276
+ assert(
1277
+ this.client.details.type !== summarizerClientType &&
1278
+ createNewResolvedUrl !== undefined,
1279
+ 0x2c4 /* "client should not be summarizer before container is created" */,
1280
+ );
1281
+ this.service = await runWithRetry(
1282
+ async () =>
1283
+ this.serviceFactory.createContainer(
1284
+ summary,
1285
+ createNewResolvedUrl,
1286
+ this.subLogger,
1287
+ false, // clientIsSummarizer
1288
+ ),
1289
+ "containerAttach",
1290
+ this.mc.logger,
1291
+ {
1292
+ cancel: this._deltaManager.closeAbortController.signal,
1293
+ }, // progress
1294
+ );
1295
+ }
1296
+ this.storageAdapter.connectToService(this.service);
1297
+ return this.storageAdapter;
1298
+ };
1299
+
1300
+ let attachP = runRetriableAttachProcess({
1301
+ initialAttachmentData: this.attachmentData,
1302
+ offlineLoadEnabled: this.serializedStateManager.offlineLoadEnabled,
1303
+ detachedBlobStorage: this.detachedBlobStorage,
1304
+ setAttachmentData,
1305
+ createAttachmentSummary,
1306
+ createOrGetStorageService,
1307
+ });
1315
1308
 
1316
- await this.storageAdapter.uploadSummaryWithContext(summary, {
1317
- referenceSequenceNumber: 0,
1318
- ackHandle: undefined,
1319
- proposalHandle: undefined,
1309
+ // only enable the new behavior if the config is set
1310
+ if (
1311
+ this.mc.config.getBoolean("Fluid.Container.RetryOnAttachFailure") !== true
1312
+ ) {
1313
+ attachP = attachP.catch((error) => {
1314
+ throw normalizeErrorAndClose(error);
1320
1315
  });
1321
1316
  }
1322
1317
 
1323
- this._attachState = AttachState.Attached;
1324
- this.runtime.setAttachState(AttachState.Attached);
1325
- this.emit("attached");
1326
-
1318
+ this.serializedStateManager.setSnapshot(await attachP);
1327
1319
  if (!this.closed) {
1328
1320
  this.handleDeltaConnectionArg(
1329
1321
  {
@@ -1333,17 +1325,11 @@ export class Container
1333
1325
  attachProps?.deltaConnection,
1334
1326
  );
1335
1327
  }
1336
- } catch (error) {
1337
- // add resolved URL on error object so that host has the ability to find this document and delete it
1338
- const newError = normalizeError(error);
1339
- newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
1340
- this.close(newError);
1341
- throw newError;
1342
- }
1343
- },
1344
- { start: true, end: true, cancel: "generic" },
1345
- );
1346
- }
1328
+ },
1329
+ { start: true, end: true, cancel: "generic" },
1330
+ );
1331
+ },
1332
+ );
1347
1333
 
1348
1334
  private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
1349
1335
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
@@ -1370,7 +1356,7 @@ export class Container
1370
1356
  public connect() {
1371
1357
  if (this.closed) {
1372
1358
  throw new UsageError(`The Container is closed and cannot be connected`);
1373
- } else if (this._attachState !== AttachState.Attached) {
1359
+ } else if (this.attachState !== AttachState.Attached) {
1374
1360
  throw new UsageError(`The Container is not attached and cannot be connected`);
1375
1361
  } else if (!this.connected) {
1376
1362
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
@@ -1386,7 +1372,7 @@ export class Container
1386
1372
  private connectInternal(args: IConnectionArgs) {
1387
1373
  assert(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
1388
1374
  assert(
1389
- this._attachState === AttachState.Attached,
1375
+ this.attachState === AttachState.Attached,
1390
1376
  0x2c6 /* "Attempting to connect() a container that is not attached" */,
1391
1377
  );
1392
1378
 
@@ -1521,11 +1507,6 @@ export class Container
1521
1507
  return true;
1522
1508
  }
1523
1509
 
1524
- private async getVersion(version: string | null): Promise<IVersion | undefined> {
1525
- const versions = await this.storageAdapter.getVersions(version, 1);
1526
- return versions[0];
1527
- }
1528
-
1529
1510
  private connectToDeltaStream(args: IConnectionArgs) {
1530
1511
  // All agents need "write" access, including summarizer.
1531
1512
  if (!this._canReconnect || !this.client.details.capabilities.interactive) {
@@ -1535,6 +1516,22 @@ export class Container
1535
1516
  this._deltaManager.connect(args);
1536
1517
  }
1537
1518
 
1519
+ private readonly metadataUpdateHandler = (metadata: Record<string, string>) => {
1520
+ this._containerMetadata = { ...this._containerMetadata, ...metadata };
1521
+ this.emit("metadataUpdate", metadata);
1522
+ };
1523
+
1524
+ private async createDocumentService(
1525
+ serviceProvider: () => Promise<IDocumentService>,
1526
+ ): Promise<IDocumentService> {
1527
+ const service = await serviceProvider();
1528
+ // Back-compat for Old driver
1529
+ if (service.on !== undefined) {
1530
+ service.on("metadataUpdate", this.metadataUpdateHandler);
1531
+ }
1532
+ return service;
1533
+ }
1534
+
1538
1535
  /**
1539
1536
  * Load container.
1540
1537
  *
@@ -1548,10 +1545,12 @@ export class Container
1548
1545
  loadToSequenceNumber: number | undefined,
1549
1546
  ) {
1550
1547
  const timings: Record<string, number> = { phase1: performance.now() };
1551
- this.service = await this.serviceFactory.createDocumentService(
1552
- resolvedUrl,
1553
- this.subLogger,
1554
- this.client.details.type === summarizerClientType,
1548
+ this.service = await this.createDocumentService(async () =>
1549
+ this.serviceFactory.createDocumentService(
1550
+ resolvedUrl,
1551
+ this.subLogger,
1552
+ this.client.details.type === summarizerClientType,
1553
+ ),
1555
1554
  );
1556
1555
 
1557
1556
  // Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
@@ -1574,33 +1573,20 @@ export class Container
1574
1573
 
1575
1574
  this.storageAdapter.connectToService(this.service);
1576
1575
 
1577
- this._attachState = AttachState.Attached;
1576
+ this.attachmentData = {
1577
+ state: AttachState.Attached,
1578
+ };
1578
1579
 
1579
1580
  timings.phase2 = performance.now();
1580
1581
  // Fetch specified snapshot.
1581
- const { snapshot, versionId } =
1582
- pendingLocalState === undefined
1583
- ? await this.fetchSnapshotTree(specifiedVersion)
1584
- : { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
1585
-
1586
- if (pendingLocalState) {
1587
- this.baseSnapshot = pendingLocalState.baseSnapshot;
1588
- this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
1589
- } else {
1590
- assert(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
1591
- if (this.offlineLoadEnabled) {
1592
- this.baseSnapshot = snapshot;
1593
- // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
1594
- this.baseSnapshotBlobs = await getBlobContentsFromTree(
1595
- snapshot,
1596
- this.storageAdapter,
1597
- );
1598
- }
1599
- }
1600
-
1582
+ const { snapshotTree, version } = await this.serializedStateManager.fetchSnapshot(
1583
+ specifiedVersion,
1584
+ this.service?.policies?.supportGetSnapshotApi,
1585
+ );
1586
+ this._loadedFromVersion = version;
1601
1587
  const attributes: IDocumentAttributes = await this.getDocumentAttributes(
1602
1588
  this.storageAdapter,
1603
- snapshot,
1589
+ snapshotTree,
1604
1590
  );
1605
1591
 
1606
1592
  // If we saved ops, we will replay them and don't need DeltaManager to fetch them
@@ -1687,15 +1673,20 @@ export class Container
1687
1673
 
1688
1674
  // ...load in the existing quorum
1689
1675
  // Initialize the protocol handler
1690
- await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1676
+ await this.initializeProtocolStateFromSnapshot(
1677
+ attributes,
1678
+ this.storageAdapter,
1679
+ snapshotTree,
1680
+ );
1691
1681
 
1692
1682
  timings.phase3 = performance.now();
1693
1683
  const codeDetails = this.getCodeDetailsFromQuorum();
1694
1684
  await this.instantiateRuntime(
1695
1685
  codeDetails,
1696
- snapshot,
1686
+ snapshotTree,
1697
1687
  // give runtime a dummy value so it knows we're loading from a stash blob
1698
1688
  pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined,
1689
+ isInstanceOfISnapshot(snapshotTree) ? snapshotTree : undefined,
1699
1690
  );
1700
1691
 
1701
1692
  // replay saved ops
@@ -1778,7 +1769,7 @@ export class Container
1778
1769
  );
1779
1770
  return {
1780
1771
  sequenceNumber: attributes.sequenceNumber,
1781
- version: versionId,
1772
+ version: version?.id,
1782
1773
  dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
1783
1774
  dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
1784
1775
  };
@@ -1808,24 +1799,30 @@ export class Container
1808
1799
  this.setLoaded();
1809
1800
  }
1810
1801
 
1811
- private async rehydrateDetachedFromSnapshot(detachedContainerSnapshot: ISummaryTree) {
1812
- if (detachedContainerSnapshot.tree[hasBlobsSummaryTree] !== undefined) {
1802
+ private async rehydrateDetachedFromSnapshot({
1803
+ baseSnapshot,
1804
+ snapshotBlobs,
1805
+ hasAttachmentBlobs,
1806
+ pendingRuntimeState,
1807
+ }: IPendingDetachedContainerState) {
1808
+ if (hasAttachmentBlobs) {
1813
1809
  assert(
1814
1810
  !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1815
1811
  0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
1816
1812
  );
1817
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1818
- delete detachedContainerSnapshot.tree[hasBlobsSummaryTree];
1819
1813
  }
1820
-
1821
- const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
1822
- this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
1823
- const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
1814
+ const snapshotTreeWithBlobContents: ISnapshotTreeWithBlobContents =
1815
+ combineSnapshotTreeAndSnapshotBlobs(baseSnapshot, snapshotBlobs);
1816
+ this.storageAdapter.loadSnapshotFromSnapshotBlobs(snapshotBlobs);
1817
+ const attributes = await this.getDocumentAttributes(
1818
+ this.storageAdapter,
1819
+ snapshotTreeWithBlobContents,
1820
+ );
1824
1821
 
1825
1822
  await this.attachDeltaManagerOpHandler(attributes);
1826
1823
 
1827
1824
  // Initialize the protocol handler
1828
- const baseTree = getProtocolSnapshotTree(snapshotTree);
1825
+ const baseTree = getProtocolSnapshotTree(snapshotTreeWithBlobContents);
1829
1826
  const qValues = await readAndParse<[string, ICommittedProposal][]>(
1830
1827
  this.storageAdapter,
1831
1828
  baseTree.blobs.quorumValues,
@@ -1840,7 +1837,11 @@ export class Container
1840
1837
  );
1841
1838
  const codeDetails = this.getCodeDetailsFromQuorum();
1842
1839
 
1843
- await this.instantiateRuntime(codeDetails, snapshotTree);
1840
+ await this.instantiateRuntime(
1841
+ codeDetails,
1842
+ snapshotTreeWithBlobContents,
1843
+ pendingRuntimeState,
1844
+ );
1844
1845
 
1845
1846
  this.setLoaded();
1846
1847
  }
@@ -1984,13 +1985,12 @@ export class Container
1984
1985
 
1985
1986
  private static setupClient(
1986
1987
  containerId: string,
1987
- options?: ILoaderOptions,
1988
+ loaderOptionsClient?: IClient,
1988
1989
  clientDetailsOverride?: IClientDetails,
1989
1990
  ): IClient {
1990
- const loaderOptionsClient = structuredClone(options?.client);
1991
1991
  const client: IClient =
1992
1992
  loaderOptionsClient !== undefined
1993
- ? (loaderOptionsClient as IClient)
1993
+ ? structuredClone(loaderOptionsClient)
1994
1994
  : {
1995
1995
  details: {
1996
1996
  capabilities: { interactive: true },
@@ -2068,10 +2068,10 @@ export class Container
2068
2068
  this.connectionStateHandler.cancelEstablishingConnection(reason);
2069
2069
  });
2070
2070
 
2071
- deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
2071
+ deltaManager.on("disconnect", (text, error) => {
2072
2072
  this.noopHeuristic?.notifyDisconnect();
2073
2073
  if (!this.closed) {
2074
- this.connectionStateHandler.receivedDisconnectEvent(reason);
2074
+ this.connectionStateHandler.receivedDisconnectEvent({ text, error });
2075
2075
  }
2076
2076
  });
2077
2077
 
@@ -2295,9 +2295,6 @@ export class Container
2295
2295
  }
2296
2296
 
2297
2297
  private processRemoteMessage(message: ISequencedDocumentMessage) {
2298
- if (this.offlineLoadEnabled) {
2299
- this.savedOps.push(message);
2300
- }
2301
2298
  const local = this.clientId === message.clientId;
2302
2299
 
2303
2300
  // Allow the protocol handler to process the message
@@ -2305,7 +2302,7 @@ export class Container
2305
2302
 
2306
2303
  // Forward messages to the loaded runtime for processing
2307
2304
  this.runtime.process(message, local);
2308
-
2305
+ this.serializedStateManager.addProcessedOp(message);
2309
2306
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
2310
2307
  if (this.activeConnection()) {
2311
2308
  if (this.noopHeuristic === undefined) {
@@ -2358,36 +2355,11 @@ export class Container
2358
2355
  }
2359
2356
  }
2360
2357
 
2361
- /**
2362
- * Get the most recent snapshot, or a specific version.
2363
- * @param specifiedVersion - The specific version of the snapshot to retrieve
2364
- * @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
2365
- */
2366
- private async fetchSnapshotTree(
2367
- specifiedVersion: string | undefined,
2368
- ): Promise<{ snapshot?: ISnapshotTree; versionId?: string }> {
2369
- const version = await this.getVersion(specifiedVersion ?? null);
2370
-
2371
- if (version === undefined && specifiedVersion !== undefined) {
2372
- // We should have a defined version to load from if specified version requested
2373
- this.mc.logger.sendErrorEvent({
2374
- eventName: "NoVersionFoundWhenSpecified",
2375
- id: specifiedVersion,
2376
- });
2377
- }
2378
- this._loadedFromVersion = version;
2379
- const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
2380
-
2381
- if (snapshot === undefined && version !== undefined) {
2382
- this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
2383
- }
2384
- return { snapshot, versionId: version?.id };
2385
- }
2386
-
2387
2358
  private async instantiateRuntime(
2388
2359
  codeDetails: IFluidCodeDetails,
2389
- snapshot: ISnapshotTree | undefined,
2360
+ snapshotTree: ISnapshotTree | undefined,
2390
2361
  pendingLocalState?: unknown,
2362
+ snapshot?: ISnapshot,
2391
2363
  ) {
2392
2364
  assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
2393
2365
 
@@ -2421,12 +2393,12 @@ export class Container
2421
2393
  (this.protocolHandler.quorum.get("code") ??
2422
2394
  this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
2423
2395
 
2424
- const existing = snapshot !== undefined;
2396
+ const existing = snapshotTree !== undefined;
2425
2397
 
2426
2398
  const context = new ContainerContext(
2427
2399
  this.options,
2428
2400
  this.scope,
2429
- snapshot,
2401
+ snapshotTree,
2430
2402
  this._loadedFromVersion,
2431
2403
  this._deltaManager,
2432
2404
  this.storageAdapter,
@@ -2453,6 +2425,7 @@ export class Container
2453
2425
  existing,
2454
2426
  this.subLogger,
2455
2427
  pendingLocalState,
2428
+ snapshot,
2456
2429
  );
2457
2430
 
2458
2431
  this._runtime = await PerformanceEvent.timedExecAsync(