@fluidframework/container-loader 2.0.0-rc.1.0.4 → 2.0.0-rc.2.0.0

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 +219 -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} +179 -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 +63 -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 +297 -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,41 +87,45 @@ import {
86
87
  formatTick,
87
88
  GenericError,
88
89
  UsageError,
90
+ IFluidErrorBase,
91
+ type TelemetryEventCategory,
89
92
  } from "@fluidframework/telemetry-utils";
90
- import { Audience } from "./audience";
91
- import { ContainerContext } from "./containerContext";
93
+ import structuredClone from "@ungap/structured-clone";
94
+ import { Audience } from "./audience.js";
95
+ import { ContainerContext } from "./containerContext.js";
92
96
  import {
93
97
  ReconnectMode,
94
98
  IConnectionManagerFactoryArgs,
95
99
  getPackageName,
96
100
  IConnectionDetailsInternal,
97
101
  IConnectionStateChangeReason,
98
- } from "./contracts";
99
- import { DeltaManager, IConnectionArgs } from "./deltaManager";
100
- import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
101
- import { pkgVersion } from "./packageVersion";
102
- import {
103
- ContainerStorageAdapter,
104
- getBlobContentsFromTree,
105
- getBlobContentsFromTreeWithBlobContents,
106
- ISerializableBlobContents,
107
- } from "./containerStorageAdapter";
108
- 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";
109
108
  import {
109
+ ISnapshotTreeWithBlobContents,
110
110
  combineAppAndProtocolSummary,
111
111
  getProtocolSnapshotTree,
112
- getSnapshotTreeFromSerializedContainer,
113
- } from "./utils";
114
- import { initQuorumValuesFromCodeDetails } from "./quorum";
115
- import { NoopHeuristic } from "./noopHeuristic";
116
- import { ConnectionManager } from "./connectionManager";
117
- 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";
118
121
  import {
119
122
  IProtocolHandler,
120
123
  ProtocolHandler,
121
124
  ProtocolHandlerBuilder,
122
125
  protocolHandlerShouldProcessSignal,
123
- } from "./protocol";
126
+ } from "./protocol.js";
127
+ import { AttachProcessProps, AttachmentData, runRetriableAttachProcess } from "./attachment.js";
128
+ import { SerializedStateManager } from "./serializedStateManager.js";
124
129
 
125
130
  const detachedContainerRefSeqNumber = 0;
126
131
 
@@ -129,8 +134,6 @@ const savedContainerEvent = "saved";
129
134
 
130
135
  const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
131
136
 
132
- const hasBlobsSummaryTree = ".hasAttachmentBlobs";
133
-
134
137
  /**
135
138
  * @internal
136
139
  */
@@ -312,9 +315,7 @@ export async function waitContainerToCatchUp(container: IContainer) {
312
315
  });
313
316
  }
314
317
 
315
- const getCodeProposal =
316
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
317
- (quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
318
+ const getCodeProposal = (quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
318
319
 
319
320
  /**
320
321
  * Helper function to report to telemetry cases where operation takes longer than expected (200ms)
@@ -325,7 +326,7 @@ const getCodeProposal =
325
326
  export async function ReportIfTooLong(
326
327
  logger: ITelemetryLoggerExt,
327
328
  eventName: string,
328
- action: () => Promise<ITelemetryProperties>,
329
+ action: () => Promise<ITelemetryBaseProperties>,
329
330
  ) {
330
331
  const event = PerformanceEvent.start(logger, { eventName });
331
332
  const props = await action();
@@ -340,6 +341,7 @@ export async function ReportIfTooLong(
340
341
  * @internal
341
342
  */
342
343
  export interface IPendingContainerState {
344
+ attached: true;
343
345
  pendingRuntimeState: unknown;
344
346
  /**
345
347
  * Snapshot from which container initially loaded.
@@ -360,6 +362,19 @@ export interface IPendingContainerState {
360
362
  clientId?: string;
361
363
  }
362
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
+
363
378
  const summarizerClientType = "summarizer";
364
379
 
365
380
  interface IContainerLifecycleEvents extends IEvent {
@@ -373,7 +388,6 @@ export class Container
373
388
  {
374
389
  /**
375
390
  * Load an existing container.
376
- * @internal
377
391
  */
378
392
  public static async load(
379
393
  loadProps: IContainerLoadProps,
@@ -471,12 +485,9 @@ export class Container
471
485
  container.mc.logger,
472
486
  { eventName: "RehydrateDetachedFromSnapshot" },
473
487
  async (_event) => {
474
- const deserializedSummary = JSON.parse(snapshot);
475
- if (!isCombinedAppAndProtocolSummary(deserializedSummary, hasBlobsSummaryTree)) {
476
- throw new UsageError("Cannot rehydrate detached container. Incorrect format");
477
- }
478
-
479
- await container.rehydrateDetachedFromSnapshot(deserializedSummary);
488
+ const detachedContainerState: IPendingDetachedContainerState =
489
+ getDetachedContainerStateFromSerializedContainer(snapshot);
490
+ await container.rehydrateDetachedFromSnapshot(detachedContainerState);
480
491
  return container;
481
492
  },
482
493
  { start: true, end: true, cancel: "generic" },
@@ -501,7 +512,6 @@ export class Container
501
512
 
502
513
  /**
503
514
  * Used by the RelativeLoader to spawn a new Container for the same document. Used to create the summarizing client.
504
- * @internal
505
515
  */
506
516
  public readonly clone: (
507
517
  loadProps: IContainerLoadProps,
@@ -551,8 +561,6 @@ export class Container
551
561
  return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
552
562
  }
553
563
 
554
- private _attachState = AttachState.Detached;
555
-
556
564
  private readonly storageAdapter: ContainerStorageAdapter;
557
565
 
558
566
  private readonly _deltaManager: DeltaManager<ConnectionManager>;
@@ -578,17 +586,16 @@ export class Container
578
586
  private firstConnection = true;
579
587
  private readonly connectionTransitionTimes: number[] = [];
580
588
  private _loadedFromVersion: IVersion | undefined;
581
- private attachStarted = false;
582
589
  private _dirtyContainer = false;
583
- private readonly savedOps: ISequencedDocumentMessage[] = [];
584
- private baseSnapshot?: ISnapshotTree;
585
- private baseSnapshotBlobs?: ISerializableBlobContents;
590
+ private attachmentData: AttachmentData = { state: AttachState.Detached };
591
+ private readonly serializedStateManager: SerializedStateManager;
586
592
  private readonly _containerId: string;
587
593
 
588
594
  private lastVisible: number | undefined;
589
595
  private readonly visibilityEventHandler: (() => void) | undefined;
590
596
  private readonly connectionStateHandler: IConnectionStateHandler;
591
597
  private readonly clientsWhoShouldHaveLeft = new Set<string>();
598
+ private _containerMetadata: Readonly<Record<string, string>> = {};
592
599
 
593
600
  private setAutoReconnectTime = performance.now();
594
601
 
@@ -617,6 +624,10 @@ export class Container
617
624
  return this._deltaManager.readOnlyInfo;
618
625
  }
619
626
 
627
+ public get containerMetadata(): Record<string, string> {
628
+ return this._containerMetadata;
629
+ }
630
+
620
631
  /**
621
632
  * Sends signal to runtime (and data stores) to be read-only.
622
633
  * Hosts may have read only views, indicating to data stores that no edits are allowed.
@@ -660,12 +671,8 @@ export class Container
660
671
  return this._clientId;
661
672
  }
662
673
 
663
- private get offlineLoadEnabled(): boolean {
664
- const enabled =
665
- this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
666
- this.options?.enableOfflineLoad === true;
667
- // summarizer will not have any pending state we want to save
668
- return enabled && this.deltaManager.clientDetails.capabilities.interactive;
674
+ private get isInteractiveClient(): boolean {
675
+ return this.deltaManager.clientDetails.capabilities.interactive;
669
676
  }
670
677
 
671
678
  /**
@@ -734,9 +741,6 @@ export class Container
734
741
 
735
742
  private readonly _lifecycleEvents = new TypedEventEmitter<IContainerLifecycleEvents>();
736
743
 
737
- /**
738
- * @internal
739
- */
740
744
  constructor(
741
745
  createProps: IContainerCreateProps,
742
746
  loadProps?: Pick<IContainerLoadProps, "pendingLocalState">,
@@ -809,7 +813,7 @@ export class Container
809
813
 
810
814
  this.client = Container.setupClient(
811
815
  this._containerId,
812
- this.options,
816
+ options.client,
813
817
  this.clientDetailsOverride,
814
818
  );
815
819
 
@@ -829,7 +833,7 @@ export class Container
829
833
  clientType, // Differentiating summarizer container from main container
830
834
  containerId: this._containerId,
831
835
  docId: () => this.resolvedUrl?.id,
832
- containerAttachState: () => this._attachState,
836
+ containerAttachState: () => this.attachState,
833
837
  containerLifecycleState: () => this._lifecycleState,
834
838
  containerConnectionState: () => ConnectionState[this.connectionState],
835
839
  serializedContainer: pendingLocalState !== undefined,
@@ -886,7 +890,7 @@ export class Container
886
890
  logConnectionIssue: (
887
891
  eventName: string,
888
892
  category: TelemetryEventCategory,
889
- details?: ITelemetryProperties,
893
+ details?: ITelemetryBaseProperties,
890
894
  ) => {
891
895
  const mode = this.connectionMode;
892
896
  // We get here when socket does not receive any ops on "write" connection, including
@@ -952,6 +956,17 @@ export class Container
952
956
  forceEnableSummarizeProtocolTree,
953
957
  );
954
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
+
955
970
  const isDomAvailable =
956
971
  typeof document === "object" &&
957
972
  document !== null &&
@@ -1030,6 +1045,11 @@ export class Container
1030
1045
 
1031
1046
  this._lifecycleState = "closing";
1032
1047
 
1048
+ // Back-compat for Old driver
1049
+ if (this.service?.off !== undefined) {
1050
+ this.service?.off("metadataUpdate", this.metadataUpdateHandler);
1051
+ }
1052
+
1033
1053
  this._protocolHandler?.close();
1034
1054
 
1035
1055
  this.connectionStateHandler.dispose();
@@ -1127,202 +1147,175 @@ export class Container
1127
1147
  }
1128
1148
 
1129
1149
  private async getPendingLocalStateCore(props: IGetPendingLocalStateProps) {
1130
- return PerformanceEvent.timedExecAsync(
1131
- this.mc.logger,
1132
- {
1133
- eventName: "getPendingLocalState",
1134
- notifyImminentClosure: props.notifyImminentClosure,
1135
- savedOpsSize: this.savedOps.length,
1136
- clientId: this.clientId,
1137
- },
1138
- async () => {
1139
- if (!this.offlineLoadEnabled) {
1140
- throw new UsageError(
1141
- "Can't get pending local state unless offline load is enabled",
1142
- );
1143
- }
1144
- if (this.closed || this._disposed) {
1145
- throw new UsageError(
1146
- "Pending state cannot be retried if the container is closed or disposed",
1147
- );
1148
- }
1149
- assert(
1150
- this.attachState === AttachState.Attached,
1151
- 0x0d1 /* "Container should be attached before close" */,
1152
- );
1153
- assert(
1154
- this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
1155
- 0x0d2 /* "resolved url should be valid Fluid url" */,
1156
- );
1157
- assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
1158
- assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
1159
- const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
1160
- const pendingState: IPendingContainerState = {
1161
- pendingRuntimeState,
1162
- baseSnapshot: this.baseSnapshot,
1163
- snapshotBlobs: this.baseSnapshotBlobs,
1164
- savedOps: this.savedOps,
1165
- url: this.resolvedUrl.url,
1166
- // no need to save this if there is no pending runtime state
1167
- clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
1168
- };
1169
-
1170
- return JSON.stringify(pendingState);
1171
- },
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,
1172
1168
  );
1169
+ return pendingState;
1173
1170
  }
1174
1171
 
1175
1172
  public get attachState(): AttachState {
1176
- return this._attachState;
1173
+ return this.attachmentData.state;
1177
1174
  }
1178
1175
 
1179
1176
  public serialize(): string {
1180
- assert(
1181
- this.attachState === AttachState.Detached,
1182
- 0x0d3 /* "Should only be called in detached container" */,
1183
- );
1184
-
1185
- const appSummary: ISummaryTree = this.runtime.createSummary();
1186
- const protocolSummary = this.captureProtocolSummary();
1187
- const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1188
-
1189
- if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
1190
- combinedSummary.tree[hasBlobsSummaryTree] = {
1191
- type: SummaryType.Blob,
1192
- content: "true",
1193
- };
1177
+ if (this.attachmentData.state === AttachState.Attached || this.closed) {
1178
+ throw new UsageError("Container must not be attached or closed.");
1194
1179
  }
1195
- return JSON.stringify(combinedSummary);
1196
- }
1197
1180
 
1198
- public async attach(
1199
- request: IRequest,
1200
- attachProps?: { deltaConnection?: "none" | "delayed" },
1201
- ): Promise<void> {
1202
- await PerformanceEvent.timedExecAsync(
1203
- this.mc.logger,
1204
- { eventName: "Attach" },
1205
- async () => {
1206
- if (this._lifecycleState !== "loaded") {
1207
- // pre-0.58 error message: containerNotValidForAttach
1208
- throw new UsageError(
1209
- `The Container is not in a valid state for attach [${this._lifecycleState}]`,
1210
- );
1211
- }
1181
+ const attachingData =
1182
+ this.attachmentData.state === AttachState.Attaching ? this.attachmentData : undefined;
1212
1183
 
1213
- // If container is already attached or attach is in progress, throw an error.
1214
- assert(
1215
- this._attachState === AttachState.Detached && !this.attachStarted,
1216
- 0x205 /* "attach() called more than once" */,
1217
- );
1218
- this.attachStarted = true;
1184
+ const combinedSummary =
1185
+ attachingData?.summary ??
1186
+ combineAppAndProtocolSummary(
1187
+ this.runtime.createSummary(),
1188
+ this.captureProtocolSummary(),
1189
+ );
1219
1190
 
1220
- // If attachment blobs were uploaded in detached state we will go through a different attach flow
1221
- const hasAttachmentBlobs =
1222
- this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
1191
+ const { tree: snapshot, blobs } =
1192
+ getSnapshotTreeAndBlobsFromSerializedContainer(combinedSummary);
1223
1193
 
1224
- try {
1225
- assert(
1226
- this.deltaManager.inbound.length === 0,
1227
- 0x0d6 /* "Inbound queue should be empty when attaching" */,
1228
- );
1194
+ const pendingRuntimeState =
1195
+ attachingData !== undefined ? this.runtime.getPendingLocalState() : undefined;
1196
+ assert(!isPromiseLike(pendingRuntimeState), 0x8e3 /* should not be a promise */);
1229
1197
 
1230
- let summary: ISummaryTree;
1231
- if (!hasAttachmentBlobs) {
1232
- // Get the document state post attach - possibly can just call attach but we need to change the
1233
- // semantics around what the attach means as far as async code goes.
1234
- const appSummary: ISummaryTree = this.runtime.createSummary();
1235
- const protocolSummary = this.captureProtocolSummary();
1236
- summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1237
-
1238
- // Set the state as attaching as we are starting the process of attaching container.
1239
- // This should be fired after taking the summary because it is the place where we are
1240
- // starting to attach the container to storage.
1241
- // Also, this should only be fired in detached container.
1242
- this._attachState = AttachState.Attaching;
1243
- this.runtime.setAttachState(AttachState.Attaching);
1244
- this.emit("attaching");
1245
- if (this.offlineLoadEnabled) {
1246
- const snapshot = getSnapshotTreeFromSerializedContainer(summary);
1247
- this.baseSnapshot = snapshot;
1248
- this.baseSnapshotBlobs =
1249
- getBlobContentsFromTreeWithBlobContents(snapshot);
1250
- }
1251
- }
1252
-
1253
- // Actually go and create the resolved document
1254
- if (this.service === undefined) {
1255
- const createNewResolvedUrl = await this.urlResolver.resolve(request);
1256
- assert(
1257
- this.client.details.type !== summarizerClientType &&
1258
- createNewResolvedUrl !== undefined,
1259
- 0x2c4 /* "client should not be summarizer before container is created" */,
1260
- );
1261
- this.service = await runWithRetry(
1262
- async () =>
1263
- this.serviceFactory.createContainer(
1264
- summary,
1265
- createNewResolvedUrl,
1266
- this.subLogger,
1267
- false, // clientIsSummarizer
1268
- ),
1269
- "containerAttach",
1270
- this.mc.logger,
1271
- {
1272
- cancel: this._deltaManager.closeAbortController.signal,
1273
- }, // 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}]`,
1274
1224
  );
1275
1225
  }
1276
- this.storageAdapter.connectToService(this.service);
1277
1226
 
1278
- if (hasAttachmentBlobs) {
1279
- // upload blobs to storage
1280
- assert(
1281
- !!this.detachedBlobStorage,
1282
- 0x24e /* "assertion for type narrowing" */,
1283
- );
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
+ };
1284
1236
 
1285
- // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
1286
- // support blob handles that only know about the local IDs
1287
- const redirectTable = new Map<string, string>();
1288
- // if new blobs are added while uploading, upload them too
1289
- while (redirectTable.size < this.detachedBlobStorage.size) {
1290
- const newIds = this.detachedBlobStorage
1291
- .getBlobIds()
1292
- .filter((id) => !redirectTable.has(id));
1293
- for (const id of newIds) {
1294
- const blob = await this.detachedBlobStorage.readBlob(id);
1295
- const response = await this.storageAdapter.createBlob(blob);
1296
- 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);
1297
1249
  }
1298
1250
  }
1251
+ };
1299
1252
 
1300
- // take summary and upload
1301
- const appSummary: ISummaryTree = this.runtime.createSummary(redirectTable);
1302
- const protocolSummary = this.captureProtocolSummary();
1303
- summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1304
-
1305
- this._attachState = AttachState.Attaching;
1306
- this.runtime.setAttachState(AttachState.Attaching);
1307
- this.emit("attaching");
1308
- if (this.offlineLoadEnabled) {
1309
- const snapshot = getSnapshotTreeFromSerializedContainer(summary);
1310
- this.baseSnapshot = snapshot;
1311
- this.baseSnapshotBlobs =
1312
- 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);
1313
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
+ });
1314
1308
 
1315
- await this.storageAdapter.uploadSummaryWithContext(summary, {
1316
- referenceSequenceNumber: 0,
1317
- ackHandle: undefined,
1318
- 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);
1319
1315
  });
1320
1316
  }
1321
1317
 
1322
- this._attachState = AttachState.Attached;
1323
- this.runtime.setAttachState(AttachState.Attached);
1324
- this.emit("attached");
1325
-
1318
+ this.serializedStateManager.setSnapshot(await attachP);
1326
1319
  if (!this.closed) {
1327
1320
  this.handleDeltaConnectionArg(
1328
1321
  {
@@ -1332,17 +1325,11 @@ export class Container
1332
1325
  attachProps?.deltaConnection,
1333
1326
  );
1334
1327
  }
1335
- } catch (error) {
1336
- // add resolved URL on error object so that host has the ability to find this document and delete it
1337
- const newError = normalizeError(error);
1338
- newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
1339
- this.close(newError);
1340
- throw newError;
1341
- }
1342
- },
1343
- { start: true, end: true, cancel: "generic" },
1344
- );
1345
- }
1328
+ },
1329
+ { start: true, end: true, cancel: "generic" },
1330
+ );
1331
+ },
1332
+ );
1346
1333
 
1347
1334
  private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
1348
1335
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
@@ -1369,7 +1356,7 @@ export class Container
1369
1356
  public connect() {
1370
1357
  if (this.closed) {
1371
1358
  throw new UsageError(`The Container is closed and cannot be connected`);
1372
- } else if (this._attachState !== AttachState.Attached) {
1359
+ } else if (this.attachState !== AttachState.Attached) {
1373
1360
  throw new UsageError(`The Container is not attached and cannot be connected`);
1374
1361
  } else if (!this.connected) {
1375
1362
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
@@ -1385,7 +1372,7 @@ export class Container
1385
1372
  private connectInternal(args: IConnectionArgs) {
1386
1373
  assert(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
1387
1374
  assert(
1388
- this._attachState === AttachState.Attached,
1375
+ this.attachState === AttachState.Attached,
1389
1376
  0x2c6 /* "Attempting to connect() a container that is not attached" */,
1390
1377
  );
1391
1378
 
@@ -1520,11 +1507,6 @@ export class Container
1520
1507
  return true;
1521
1508
  }
1522
1509
 
1523
- private async getVersion(version: string | null): Promise<IVersion | undefined> {
1524
- const versions = await this.storageAdapter.getVersions(version, 1);
1525
- return versions[0];
1526
- }
1527
-
1528
1510
  private connectToDeltaStream(args: IConnectionArgs) {
1529
1511
  // All agents need "write" access, including summarizer.
1530
1512
  if (!this._canReconnect || !this.client.details.capabilities.interactive) {
@@ -1534,6 +1516,22 @@ export class Container
1534
1516
  this._deltaManager.connect(args);
1535
1517
  }
1536
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
+
1537
1535
  /**
1538
1536
  * Load container.
1539
1537
  *
@@ -1547,10 +1545,12 @@ export class Container
1547
1545
  loadToSequenceNumber: number | undefined,
1548
1546
  ) {
1549
1547
  const timings: Record<string, number> = { phase1: performance.now() };
1550
- this.service = await this.serviceFactory.createDocumentService(
1551
- resolvedUrl,
1552
- this.subLogger,
1553
- 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
+ ),
1554
1554
  );
1555
1555
 
1556
1556
  // Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
@@ -1573,33 +1573,20 @@ export class Container
1573
1573
 
1574
1574
  this.storageAdapter.connectToService(this.service);
1575
1575
 
1576
- this._attachState = AttachState.Attached;
1576
+ this.attachmentData = {
1577
+ state: AttachState.Attached,
1578
+ };
1577
1579
 
1578
1580
  timings.phase2 = performance.now();
1579
1581
  // Fetch specified snapshot.
1580
- const { snapshot, versionId } =
1581
- pendingLocalState === undefined
1582
- ? await this.fetchSnapshotTree(specifiedVersion)
1583
- : { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
1584
-
1585
- if (pendingLocalState) {
1586
- this.baseSnapshot = pendingLocalState.baseSnapshot;
1587
- this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
1588
- } else {
1589
- assert(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
1590
- if (this.offlineLoadEnabled) {
1591
- this.baseSnapshot = snapshot;
1592
- // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
1593
- this.baseSnapshotBlobs = await getBlobContentsFromTree(
1594
- snapshot,
1595
- this.storageAdapter,
1596
- );
1597
- }
1598
- }
1599
-
1582
+ const { snapshotTree, version } = await this.serializedStateManager.fetchSnapshot(
1583
+ specifiedVersion,
1584
+ this.service?.policies?.supportGetSnapshotApi,
1585
+ );
1586
+ this._loadedFromVersion = version;
1600
1587
  const attributes: IDocumentAttributes = await this.getDocumentAttributes(
1601
1588
  this.storageAdapter,
1602
- snapshot,
1589
+ snapshotTree,
1603
1590
  );
1604
1591
 
1605
1592
  // If we saved ops, we will replay them and don't need DeltaManager to fetch them
@@ -1686,15 +1673,20 @@ export class Container
1686
1673
 
1687
1674
  // ...load in the existing quorum
1688
1675
  // Initialize the protocol handler
1689
- await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1676
+ await this.initializeProtocolStateFromSnapshot(
1677
+ attributes,
1678
+ this.storageAdapter,
1679
+ snapshotTree,
1680
+ );
1690
1681
 
1691
1682
  timings.phase3 = performance.now();
1692
1683
  const codeDetails = this.getCodeDetailsFromQuorum();
1693
1684
  await this.instantiateRuntime(
1694
1685
  codeDetails,
1695
- snapshot,
1686
+ snapshotTree,
1696
1687
  // give runtime a dummy value so it knows we're loading from a stash blob
1697
1688
  pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined,
1689
+ isInstanceOfISnapshot(snapshotTree) ? snapshotTree : undefined,
1698
1690
  );
1699
1691
 
1700
1692
  // replay saved ops
@@ -1777,7 +1769,7 @@ export class Container
1777
1769
  );
1778
1770
  return {
1779
1771
  sequenceNumber: attributes.sequenceNumber,
1780
- version: versionId,
1772
+ version: version?.id,
1781
1773
  dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
1782
1774
  dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
1783
1775
  };
@@ -1807,24 +1799,30 @@ export class Container
1807
1799
  this.setLoaded();
1808
1800
  }
1809
1801
 
1810
- private async rehydrateDetachedFromSnapshot(detachedContainerSnapshot: ISummaryTree) {
1811
- if (detachedContainerSnapshot.tree[hasBlobsSummaryTree] !== undefined) {
1802
+ private async rehydrateDetachedFromSnapshot({
1803
+ baseSnapshot,
1804
+ snapshotBlobs,
1805
+ hasAttachmentBlobs,
1806
+ pendingRuntimeState,
1807
+ }: IPendingDetachedContainerState) {
1808
+ if (hasAttachmentBlobs) {
1812
1809
  assert(
1813
1810
  !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1814
1811
  0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
1815
1812
  );
1816
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1817
- delete detachedContainerSnapshot.tree[hasBlobsSummaryTree];
1818
1813
  }
1819
-
1820
- const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
1821
- this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
1822
- 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
+ );
1823
1821
 
1824
1822
  await this.attachDeltaManagerOpHandler(attributes);
1825
1823
 
1826
1824
  // Initialize the protocol handler
1827
- const baseTree = getProtocolSnapshotTree(snapshotTree);
1825
+ const baseTree = getProtocolSnapshotTree(snapshotTreeWithBlobContents);
1828
1826
  const qValues = await readAndParse<[string, ICommittedProposal][]>(
1829
1827
  this.storageAdapter,
1830
1828
  baseTree.blobs.quorumValues,
@@ -1839,7 +1837,11 @@ export class Container
1839
1837
  );
1840
1838
  const codeDetails = this.getCodeDetailsFromQuorum();
1841
1839
 
1842
- await this.instantiateRuntime(codeDetails, snapshotTree);
1840
+ await this.instantiateRuntime(
1841
+ codeDetails,
1842
+ snapshotTreeWithBlobContents,
1843
+ pendingRuntimeState,
1844
+ );
1843
1845
 
1844
1846
  this.setLoaded();
1845
1847
  }
@@ -1983,13 +1985,12 @@ export class Container
1983
1985
 
1984
1986
  private static setupClient(
1985
1987
  containerId: string,
1986
- options?: ILoaderOptions,
1988
+ loaderOptionsClient?: IClient,
1987
1989
  clientDetailsOverride?: IClientDetails,
1988
1990
  ): IClient {
1989
- const loaderOptionsClient = structuredClone(options?.client);
1990
1991
  const client: IClient =
1991
1992
  loaderOptionsClient !== undefined
1992
- ? (loaderOptionsClient as IClient)
1993
+ ? structuredClone(loaderOptionsClient)
1993
1994
  : {
1994
1995
  details: {
1995
1996
  capabilities: { interactive: true },
@@ -2067,10 +2068,10 @@ export class Container
2067
2068
  this.connectionStateHandler.cancelEstablishingConnection(reason);
2068
2069
  });
2069
2070
 
2070
- deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
2071
+ deltaManager.on("disconnect", (text, error) => {
2071
2072
  this.noopHeuristic?.notifyDisconnect();
2072
2073
  if (!this.closed) {
2073
- this.connectionStateHandler.receivedDisconnectEvent(reason);
2074
+ this.connectionStateHandler.receivedDisconnectEvent({ text, error });
2074
2075
  }
2075
2076
  });
2076
2077
 
@@ -2294,9 +2295,6 @@ export class Container
2294
2295
  }
2295
2296
 
2296
2297
  private processRemoteMessage(message: ISequencedDocumentMessage) {
2297
- if (this.offlineLoadEnabled) {
2298
- this.savedOps.push(message);
2299
- }
2300
2298
  const local = this.clientId === message.clientId;
2301
2299
 
2302
2300
  // Allow the protocol handler to process the message
@@ -2304,7 +2302,7 @@ export class Container
2304
2302
 
2305
2303
  // Forward messages to the loaded runtime for processing
2306
2304
  this.runtime.process(message, local);
2307
-
2305
+ this.serializedStateManager.addProcessedOp(message);
2308
2306
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
2309
2307
  if (this.activeConnection()) {
2310
2308
  if (this.noopHeuristic === undefined) {
@@ -2357,36 +2355,11 @@ export class Container
2357
2355
  }
2358
2356
  }
2359
2357
 
2360
- /**
2361
- * Get the most recent snapshot, or a specific version.
2362
- * @param specifiedVersion - The specific version of the snapshot to retrieve
2363
- * @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
2364
- */
2365
- private async fetchSnapshotTree(
2366
- specifiedVersion: string | undefined,
2367
- ): Promise<{ snapshot?: ISnapshotTree; versionId?: string }> {
2368
- const version = await this.getVersion(specifiedVersion ?? null);
2369
-
2370
- if (version === undefined && specifiedVersion !== undefined) {
2371
- // We should have a defined version to load from if specified version requested
2372
- this.mc.logger.sendErrorEvent({
2373
- eventName: "NoVersionFoundWhenSpecified",
2374
- id: specifiedVersion,
2375
- });
2376
- }
2377
- this._loadedFromVersion = version;
2378
- const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
2379
-
2380
- if (snapshot === undefined && version !== undefined) {
2381
- this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
2382
- }
2383
- return { snapshot, versionId: version?.id };
2384
- }
2385
-
2386
2358
  private async instantiateRuntime(
2387
2359
  codeDetails: IFluidCodeDetails,
2388
- snapshot: ISnapshotTree | undefined,
2360
+ snapshotTree: ISnapshotTree | undefined,
2389
2361
  pendingLocalState?: unknown,
2362
+ snapshot?: ISnapshot,
2390
2363
  ) {
2391
2364
  assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
2392
2365
 
@@ -2420,12 +2393,12 @@ export class Container
2420
2393
  (this.protocolHandler.quorum.get("code") ??
2421
2394
  this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
2422
2395
 
2423
- const existing = snapshot !== undefined;
2396
+ const existing = snapshotTree !== undefined;
2424
2397
 
2425
2398
  const context = new ContainerContext(
2426
2399
  this.options,
2427
2400
  this.scope,
2428
- snapshot,
2401
+ snapshotTree,
2429
2402
  this._loadedFromVersion,
2430
2403
  this._deltaManager,
2431
2404
  this.storageAdapter,
@@ -2452,6 +2425,7 @@ export class Container
2452
2425
  existing,
2453
2426
  this.subLogger,
2454
2427
  pendingLocalState,
2428
+ snapshot,
2455
2429
  );
2456
2430
 
2457
2431
  this._runtime = await PerformanceEvent.timedExecAsync(