@fluidframework/container-loader 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.6.4.0.191258

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 (204) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/README.md +10 -6
  3. package/dist/audience.d.ts +1 -0
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js +5 -3
  6. package/dist/audience.js.map +1 -1
  7. package/dist/catchUpMonitor.d.ts +1 -1
  8. package/dist/catchUpMonitor.d.ts.map +1 -1
  9. package/dist/catchUpMonitor.js +2 -2
  10. package/dist/catchUpMonitor.js.map +1 -1
  11. package/dist/connectionManager.d.ts +6 -6
  12. package/dist/connectionManager.d.ts.map +1 -1
  13. package/dist/connectionManager.js +97 -93
  14. package/dist/connectionManager.js.map +1 -1
  15. package/dist/connectionStateHandler.d.ts +19 -15
  16. package/dist/connectionStateHandler.d.ts.map +1 -1
  17. package/dist/connectionStateHandler.js +59 -59
  18. package/dist/connectionStateHandler.js.map +1 -1
  19. package/dist/container.d.ts +48 -38
  20. package/dist/container.d.ts.map +1 -1
  21. package/dist/container.js +447 -325
  22. package/dist/container.js.map +1 -1
  23. package/dist/containerContext.d.ts +22 -70
  24. package/dist/containerContext.d.ts.map +1 -1
  25. package/dist/containerContext.js +24 -221
  26. package/dist/containerContext.js.map +1 -1
  27. package/dist/containerStorageAdapter.d.ts +1 -1
  28. package/dist/containerStorageAdapter.d.ts.map +1 -1
  29. package/dist/containerStorageAdapter.js +47 -16
  30. package/dist/containerStorageAdapter.js.map +1 -1
  31. package/dist/contracts.d.ts +21 -10
  32. package/dist/contracts.d.ts.map +1 -1
  33. package/dist/contracts.js +3 -3
  34. package/dist/contracts.js.map +1 -1
  35. package/dist/debugLogger.d.ts +30 -0
  36. package/dist/debugLogger.d.ts.map +1 -0
  37. package/dist/debugLogger.js +95 -0
  38. package/dist/debugLogger.js.map +1 -0
  39. package/dist/deltaManager.d.ts +21 -9
  40. package/dist/deltaManager.d.ts.map +1 -1
  41. package/dist/deltaManager.js +114 -66
  42. package/dist/deltaManager.js.map +1 -1
  43. package/dist/deltaQueue.d.ts +1 -1
  44. package/dist/deltaQueue.d.ts.map +1 -1
  45. package/dist/deltaQueue.js +10 -10
  46. package/dist/deltaQueue.js.map +1 -1
  47. package/dist/disposal.d.ts +13 -0
  48. package/dist/disposal.d.ts.map +1 -0
  49. package/dist/disposal.js +25 -0
  50. package/dist/disposal.js.map +1 -0
  51. package/dist/error.d.ts +23 -0
  52. package/dist/error.d.ts.map +1 -0
  53. package/dist/error.js +32 -0
  54. package/dist/error.js.map +1 -0
  55. package/dist/loader.d.ts +23 -5
  56. package/dist/loader.d.ts.map +1 -1
  57. package/dist/loader.js +82 -51
  58. package/dist/loader.js.map +1 -1
  59. package/dist/noopHeuristic.d.ts +23 -0
  60. package/dist/noopHeuristic.d.ts.map +1 -0
  61. package/dist/noopHeuristic.js +90 -0
  62. package/dist/noopHeuristic.js.map +1 -0
  63. package/dist/packageVersion.d.ts +1 -1
  64. package/dist/packageVersion.js +1 -1
  65. package/dist/packageVersion.js.map +1 -1
  66. package/dist/protocol.d.ts +9 -12
  67. package/dist/protocol.d.ts.map +1 -1
  68. package/dist/protocol.js +26 -7
  69. package/dist/protocol.js.map +1 -1
  70. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  71. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  72. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  73. package/dist/quorum.d.ts +1 -14
  74. package/dist/quorum.d.ts.map +1 -1
  75. package/dist/quorum.js +1 -29
  76. package/dist/quorum.js.map +1 -1
  77. package/dist/retriableDocumentStorageService.d.ts +1 -1
  78. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  79. package/dist/retriableDocumentStorageService.js +4 -4
  80. package/dist/retriableDocumentStorageService.js.map +1 -1
  81. package/dist/utils.d.ts +8 -1
  82. package/dist/utils.d.ts.map +1 -1
  83. package/dist/utils.js +30 -11
  84. package/dist/utils.js.map +1 -1
  85. package/lib/audience.d.ts +1 -0
  86. package/lib/audience.d.ts.map +1 -1
  87. package/lib/audience.js +4 -2
  88. package/lib/audience.js.map +1 -1
  89. package/lib/catchUpMonitor.d.ts +1 -1
  90. package/lib/catchUpMonitor.d.ts.map +1 -1
  91. package/lib/catchUpMonitor.js +1 -1
  92. package/lib/catchUpMonitor.js.map +1 -1
  93. package/lib/connectionManager.d.ts +6 -6
  94. package/lib/connectionManager.d.ts.map +1 -1
  95. package/lib/connectionManager.js +74 -67
  96. package/lib/connectionManager.js.map +1 -1
  97. package/lib/connectionStateHandler.d.ts +19 -15
  98. package/lib/connectionStateHandler.d.ts.map +1 -1
  99. package/lib/connectionStateHandler.js +36 -36
  100. package/lib/connectionStateHandler.js.map +1 -1
  101. package/lib/container.d.ts +48 -38
  102. package/lib/container.d.ts.map +1 -1
  103. package/lib/container.js +414 -292
  104. package/lib/container.js.map +1 -1
  105. package/lib/containerContext.d.ts +22 -70
  106. package/lib/containerContext.d.ts.map +1 -1
  107. package/lib/containerContext.js +24 -221
  108. package/lib/containerContext.js.map +1 -1
  109. package/lib/containerStorageAdapter.d.ts +1 -1
  110. package/lib/containerStorageAdapter.d.ts.map +1 -1
  111. package/lib/containerStorageAdapter.js +43 -12
  112. package/lib/containerStorageAdapter.js.map +1 -1
  113. package/lib/contracts.d.ts +21 -10
  114. package/lib/contracts.d.ts.map +1 -1
  115. package/lib/contracts.js +3 -3
  116. package/lib/contracts.js.map +1 -1
  117. package/lib/debugLogger.d.ts +30 -0
  118. package/lib/debugLogger.d.ts.map +1 -0
  119. package/lib/debugLogger.js +91 -0
  120. package/lib/debugLogger.js.map +1 -0
  121. package/lib/deltaManager.d.ts +21 -9
  122. package/lib/deltaManager.d.ts.map +1 -1
  123. package/lib/deltaManager.js +88 -37
  124. package/lib/deltaManager.js.map +1 -1
  125. package/lib/deltaQueue.d.ts +1 -1
  126. package/lib/deltaQueue.d.ts.map +1 -1
  127. package/lib/deltaQueue.js +3 -3
  128. package/lib/deltaQueue.js.map +1 -1
  129. package/lib/disposal.d.ts +13 -0
  130. package/lib/disposal.d.ts.map +1 -0
  131. package/lib/disposal.js +21 -0
  132. package/lib/disposal.js.map +1 -0
  133. package/lib/error.d.ts +23 -0
  134. package/lib/error.d.ts.map +1 -0
  135. package/lib/error.js +28 -0
  136. package/lib/error.js.map +1 -0
  137. package/lib/loader.d.ts +23 -5
  138. package/lib/loader.d.ts.map +1 -1
  139. package/lib/loader.js +82 -51
  140. package/lib/loader.js.map +1 -1
  141. package/lib/noopHeuristic.d.ts +23 -0
  142. package/lib/noopHeuristic.d.ts.map +1 -0
  143. package/lib/{collabWindowTracker.js → noopHeuristic.js} +31 -42
  144. package/lib/noopHeuristic.js.map +1 -0
  145. package/lib/packageVersion.d.ts +1 -1
  146. package/lib/packageVersion.js +1 -1
  147. package/lib/packageVersion.js.map +1 -1
  148. package/lib/protocol.d.ts +9 -12
  149. package/lib/protocol.d.ts.map +1 -1
  150. package/lib/protocol.js +24 -6
  151. package/lib/protocol.js.map +1 -1
  152. package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
  153. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  154. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  155. package/lib/quorum.d.ts +1 -14
  156. package/lib/quorum.d.ts.map +1 -1
  157. package/lib/quorum.js +0 -26
  158. package/lib/quorum.js.map +1 -1
  159. package/lib/retriableDocumentStorageService.d.ts +1 -1
  160. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  161. package/lib/retriableDocumentStorageService.js +2 -2
  162. package/lib/retriableDocumentStorageService.js.map +1 -1
  163. package/lib/utils.d.ts +8 -1
  164. package/lib/utils.d.ts.map +1 -1
  165. package/lib/utils.js +25 -7
  166. package/lib/utils.js.map +1 -1
  167. package/package.json +26 -28
  168. package/src/audience.ts +7 -1
  169. package/src/catchUpMonitor.ts +2 -2
  170. package/src/connectionManager.ts +76 -52
  171. package/src/connectionStateHandler.ts +46 -48
  172. package/src/container.ts +561 -326
  173. package/src/containerContext.ts +31 -349
  174. package/src/containerStorageAdapter.ts +49 -6
  175. package/src/contracts.ts +27 -13
  176. package/src/debugLogger.ts +113 -0
  177. package/src/deltaManager.ts +93 -36
  178. package/src/deltaQueue.ts +2 -1
  179. package/src/disposal.ts +25 -0
  180. package/src/error.ts +44 -0
  181. package/src/loader.ts +84 -36
  182. package/src/{collabWindowTracker.ts → noopHeuristic.ts} +38 -47
  183. package/src/packageVersion.ts +1 -1
  184. package/src/protocol.ts +26 -16
  185. package/src/protocolTreeDocumentStorageService.ts +1 -1
  186. package/src/quorum.ts +1 -40
  187. package/src/retriableDocumentStorageService.ts +3 -4
  188. package/src/utils.ts +33 -8
  189. package/dist/collabWindowTracker.d.ts +0 -19
  190. package/dist/collabWindowTracker.d.ts.map +0 -1
  191. package/dist/collabWindowTracker.js +0 -101
  192. package/dist/collabWindowTracker.js.map +0 -1
  193. package/dist/deltaManagerProxy.d.ts +0 -42
  194. package/dist/deltaManagerProxy.d.ts.map +0 -1
  195. package/dist/deltaManagerProxy.js +0 -79
  196. package/dist/deltaManagerProxy.js.map +0 -1
  197. package/lib/collabWindowTracker.d.ts +0 -19
  198. package/lib/collabWindowTracker.d.ts.map +0 -1
  199. package/lib/collabWindowTracker.js.map +0 -1
  200. package/lib/deltaManagerProxy.d.ts +0 -42
  201. package/lib/deltaManagerProxy.d.ts.map +0 -1
  202. package/lib/deltaManagerProxy.js +0 -74
  203. package/lib/deltaManagerProxy.js.map +0 -1
  204. package/src/deltaManagerProxy.ts +0 -109
package/dist/container.js CHANGED
@@ -11,9 +11,10 @@ exports.Container = exports.ReportIfTooLong = exports.waitContainerToCatchUp = v
11
11
  // eslint-disable-next-line import/no-internal-modules
12
12
  const merge_1 = __importDefault(require("lodash/merge"));
13
13
  const uuid_1 = require("uuid");
14
- const common_utils_1 = require("@fluidframework/common-utils");
14
+ const core_utils_1 = require("@fluidframework/core-utils");
15
+ const client_utils_1 = require("@fluid-internal/client-utils");
16
+ const core_interfaces_1 = require("@fluidframework/core-interfaces");
15
17
  const container_definitions_1 = require("@fluidframework/container-definitions");
16
- const container_utils_1 = require("@fluidframework/container-utils");
17
18
  const driver_utils_1 = require("@fluidframework/driver-utils");
18
19
  const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
19
20
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
@@ -21,20 +22,20 @@ const audience_1 = require("./audience");
21
22
  const containerContext_1 = require("./containerContext");
22
23
  const contracts_1 = require("./contracts");
23
24
  const deltaManager_1 = require("./deltaManager");
24
- const deltaManagerProxy_1 = require("./deltaManagerProxy");
25
25
  const loader_1 = require("./loader");
26
26
  const packageVersion_1 = require("./packageVersion");
27
27
  const containerStorageAdapter_1 = require("./containerStorageAdapter");
28
28
  const connectionStateHandler_1 = require("./connectionStateHandler");
29
29
  const utils_1 = require("./utils");
30
30
  const quorum_1 = require("./quorum");
31
- const collabWindowTracker_1 = require("./collabWindowTracker");
31
+ const noopHeuristic_1 = require("./noopHeuristic");
32
32
  const connectionManager_1 = require("./connectionManager");
33
33
  const connectionState_1 = require("./connectionState");
34
34
  const protocol_1 = require("./protocol");
35
35
  const detachedContainerRefSeqNumber = 0;
36
36
  const dirtyContainerEvent = "dirty";
37
37
  const savedContainerEvent = "saved";
38
+ const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
38
39
  /**
39
40
  * Waits until container connects to delta storage and gets up-to-date.
40
41
  *
@@ -54,7 +55,7 @@ const savedContainerEvent = "saved";
54
55
  async function waitContainerToCatchUp(container) {
55
56
  // Make sure we stop waiting if container is closed.
56
57
  if (container.closed) {
57
- throw new container_utils_1.UsageError("waitContainerToCatchUp: Container closed");
58
+ throw new telemetry_utils_1.UsageError("waitContainerToCatchUp: Container closed");
58
59
  }
59
60
  return new Promise((resolve, reject) => {
60
61
  const deltaManager = container.deltaManager;
@@ -62,8 +63,8 @@ async function waitContainerToCatchUp(container) {
62
63
  container.off("closed", closedCallback);
63
64
  const baseMessage = "Container closed while waiting to catch up";
64
65
  reject(err !== undefined
65
- ? (0, telemetry_utils_1.wrapError)(err, (innerMessage) => new container_utils_1.GenericError(`${baseMessage}: ${innerMessage}`))
66
- : new container_utils_1.GenericError(baseMessage));
66
+ ? (0, telemetry_utils_1.wrapError)(err, (innerMessage) => new telemetry_utils_1.GenericError(`${baseMessage}: ${innerMessage}`))
67
+ : new telemetry_utils_1.GenericError(baseMessage));
67
68
  };
68
69
  container.on("closed", closedCallback);
69
70
  // Depending on config, transition to "connected" state may include the guarantee
@@ -71,11 +72,11 @@ async function waitContainerToCatchUp(container) {
71
72
  // Waiting for "connected" state in either case gets us at least to our own Join op
72
73
  // which is a reasonable approximation of "caught up"
73
74
  const waitForOps = () => {
74
- (0, common_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp ||
75
+ (0, core_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp ||
75
76
  container.connectionState === connectionState_1.ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
76
77
  const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
77
78
  const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
78
- (0, common_utils_1.assert)(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
79
+ (0, core_utils_1.assert)(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
79
80
  if (deltaManager.lastSequenceNumber === connectionOpSeqNumber) {
80
81
  container.off("closed", closedCallback);
81
82
  resolve(hasCheckpointSequenceNumber);
@@ -111,7 +112,7 @@ async function waitContainerToCatchUp(container) {
111
112
  exports.waitContainerToCatchUp = waitContainerToCatchUp;
112
113
  const getCodeProposal =
113
114
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
114
- (quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
115
+ (quorum) => quorum.get("code") ?? quorum.get("code2");
115
116
  /**
116
117
  * Helper function to report to telemetry cases where operation takes longer than expected (200ms)
117
118
  * @param logger - logger to use
@@ -132,7 +133,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
132
133
  * @internal
133
134
  */
134
135
  constructor(createProps, loadProps) {
135
- var _a;
136
136
  super((name, error) => {
137
137
  this.mc.logger.sendErrorEvent({
138
138
  eventName: "ContainerEventHandlerException",
@@ -160,16 +160,31 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
160
160
  this.inboundQueuePausedFromInit = true;
161
161
  this.firstConnection = true;
162
162
  this.connectionTransitionTimes = [];
163
- this.messageCountAfterDisconnection = 0;
164
163
  this.attachStarted = false;
165
164
  this._dirtyContainer = false;
166
165
  this.savedOps = [];
167
- this.setAutoReconnectTime = common_utils_1.performance.now();
166
+ this.clientsWhoShouldHaveLeft = new Set();
167
+ this.setAutoReconnectTime = client_utils_1.performance.now();
168
+ this._lifecycleEvents = new client_utils_1.TypedEventEmitter();
168
169
  this._disposed = false;
170
+ this.getAbsoluteUrl = async (relativeUrl) => {
171
+ if (this.resolvedUrl === undefined) {
172
+ return undefined;
173
+ }
174
+ return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)(this._loadedCodeDetails));
175
+ };
176
+ this.updateDirtyContainerState = (dirty) => {
177
+ if (this._dirtyContainer === dirty) {
178
+ return;
179
+ }
180
+ this._dirtyContainer = dirty;
181
+ this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
182
+ };
169
183
  const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
170
- this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected] = common_utils_1.performance.now();
171
- const pendingLocalState = loadProps === null || loadProps === void 0 ? void 0 : loadProps.pendingLocalState;
172
- this._canReconnect = canReconnect !== null && canReconnect !== void 0 ? canReconnect : true;
184
+ this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected] = client_utils_1.performance.now();
185
+ const pendingLocalState = loadProps?.pendingLocalState;
186
+ this._clientId = pendingLocalState?.clientId;
187
+ this._canReconnect = canReconnect ?? true;
173
188
  this.clientDetailsOverride = clientDetailsOverride;
174
189
  this.urlResolver = urlResolver;
175
190
  this.serviceFactory = documentServiceFactory;
@@ -177,14 +192,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
177
192
  // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
178
193
  // all clients that were loaded from the same loader (including summarizer clients).
179
194
  // Tracking alternative ways to handle this in AB#4129.
180
- this.options = Object.assign({}, options);
195
+ this.options = { ...options };
181
196
  this.scope = scope;
182
197
  this.detachedBlobStorage = detachedBlobStorage;
183
198
  this.protocolHandlerBuilder =
184
- protocolHandlerBuilder !== null && protocolHandlerBuilder !== void 0 ? protocolHandlerBuilder : ((...args) => new protocol_1.ProtocolHandler(...args, new audience_1.Audience()));
199
+ protocolHandlerBuilder ??
200
+ ((attributes, quorumSnapshot, sendProposal) => new protocol_1.ProtocolHandler(attributes, quorumSnapshot, sendProposal, new audience_1.Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
185
201
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
186
202
  this.clone = async (_loadProps, createParamOverrides) => {
187
- return Container.load(_loadProps, Object.assign(Object.assign({}, createProps), createParamOverrides));
203
+ return Container.load(_loadProps, {
204
+ ...createProps,
205
+ ...createParamOverrides,
206
+ });
188
207
  };
189
208
  // Create logger for data stores to use
190
209
  const type = this.client.details.type;
@@ -192,45 +211,50 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
192
211
  const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
193
212
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
194
213
  // We assign the id later so property getter is used.
195
- this.subLogger = telemetry_utils_1.ChildLogger.create(subLogger, undefined, {
196
- all: {
197
- clientType,
198
- containerId: (0, uuid_1.v4)(),
199
- docId: () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; },
200
- containerAttachState: () => this._attachState,
201
- containerLifecycleState: () => this._lifecycleState,
202
- containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
203
- serializedContainer: pendingLocalState !== undefined,
204
- },
205
- // we need to be judicious with our logging here to avoid generating too much data
206
- // all data logged here should be broadly applicable, and not specific to a
207
- // specific error or class of errors
208
- error: {
209
- // load information to associate errors with the specific load point
210
- dmInitialSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.initialSequenceNumber; },
211
- dmLastProcessedSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastSequenceNumber; },
212
- dmLastKnownSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastKnownSeqNumber; },
213
- containerLoadedFromVersionId: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
214
- containerLoadedFromVersionDate: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
215
- // message information to associate errors with the specific execution state
216
- // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
217
- dmLastMsqSeqNumber: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.sequenceNumber; },
218
- dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
219
- dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
220
- dmLastMsgClientSeq: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientSequenceNumber; },
221
- connectionStateDuration: () => common_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
214
+ this.subLogger = (0, telemetry_utils_1.createChildLogger)({
215
+ logger: subLogger,
216
+ properties: {
217
+ all: {
218
+ clientType,
219
+ containerId: (0, uuid_1.v4)(),
220
+ docId: () => this.resolvedUrl?.id,
221
+ containerAttachState: () => this._attachState,
222
+ containerLifecycleState: () => this._lifecycleState,
223
+ containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
224
+ serializedContainer: pendingLocalState !== undefined,
225
+ },
226
+ // we need to be judicious with our logging here to avoid generating too much data
227
+ // all data logged here should be broadly applicable, and not specific to a
228
+ // specific error or class of errors
229
+ error: {
230
+ // load information to associate errors with the specific load point
231
+ dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
232
+ dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
233
+ dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
234
+ containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
235
+ containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
236
+ // message information to associate errors with the specific execution state
237
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
238
+ dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
239
+ dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
240
+ dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
241
+ ? "null"
242
+ : this.deltaManager?.lastMessage?.clientId,
243
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
244
+ connectionStateDuration: () => client_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
245
+ },
222
246
  },
223
247
  });
224
248
  // Prefix all events in this file with container-loader
225
- this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.subLogger, "Container"));
249
+ this.mc = (0, telemetry_utils_1.createChildMonitoringContext)({ logger: this.subLogger, namespace: "Container" });
226
250
  this._deltaManager = this.createDeltaManager();
227
251
  this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
228
252
  logger: this.mc.logger,
229
- connectionStateChanged: (value, oldState, reason, error) => {
253
+ connectionStateChanged: (value, oldState, reason) => {
230
254
  if (value === connectionState_1.ConnectionState.Connected) {
231
255
  this._clientId = this.connectionStateHandler.pendingClientId;
232
256
  }
233
- this.logConnectionStateChangeTelemetry(value, oldState, reason, error);
257
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
234
258
  if (this._lifecycleState === "loaded") {
235
259
  this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
236
260
  ? reason
@@ -246,9 +270,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
246
270
  // Report issues only if we already loaded container - op processing is paused while container is loading,
247
271
  // so we always time-out processing of join op in cases where fetching snapshot takes a minute.
248
272
  // It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
249
- this._deltaManager.logConnectionIssue(Object.assign({ eventName,
250
- mode, category: this._lifecycleState === "loading" ? "generic" : category, duration: common_utils_1.performance.now() -
251
- this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
273
+ this._deltaManager.logConnectionIssue({
274
+ eventName,
275
+ mode,
276
+ category: this._lifecycleState === "loading" ? "generic" : category,
277
+ duration: client_utils_1.performance.now() -
278
+ this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp],
279
+ ...(details === undefined ? {} : { details: JSON.stringify(details) }),
280
+ });
252
281
  // If this is "write" connection, it took too long to receive join op. But in most cases that's due
253
282
  // to very slow op fetches and we will eventually get there.
254
283
  // For "read" connections, we get here due to self join signal not arriving on time. We will need to
@@ -258,11 +287,15 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
258
287
  // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
259
288
  // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
260
289
  if (mode === "read") {
261
- this.disconnect();
262
- this.connect();
290
+ const reason = { text: "NoJoinSignal" };
291
+ this.disconnectInternal(reason);
292
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
263
293
  }
264
294
  },
265
- }, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
295
+ clientShouldHaveLeft: (clientId) => {
296
+ this.clientsWhoShouldHaveLeft.add(clientId);
297
+ },
298
+ }, this.deltaManager, pendingLocalState?.clientId);
266
299
  this.on(savedContainerEvent, () => {
267
300
  this.connectionStateHandler.containerSaved();
268
301
  });
@@ -271,21 +304,22 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
271
304
  // using this callback and fix them up.
272
305
  const addProtocolSummaryIfMissing = (summaryTree) => (0, driver_utils_1.isCombinedAppAndProtocolSummary)(summaryTree) === true
273
306
  ? summaryTree
274
- : (0, driver_utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
307
+ : (0, utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
275
308
  // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
276
309
  // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
277
- const forceEnableSummarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _a !== void 0 ? _a : options.summarizeProtocolTree;
278
- this.storageAdapter = new containerStorageAdapter_1.ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
310
+ const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
311
+ options.summarizeProtocolTree;
312
+ this.storageAdapter = new containerStorageAdapter_1.ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
279
313
  const isDomAvailable = typeof document === "object" &&
280
314
  document !== null &&
281
315
  typeof document.addEventListener === "function" &&
282
316
  document.addEventListener !== null;
283
- // keep track of last time page was visible for telemetry
284
- if (isDomAvailable) {
285
- this.lastVisible = document.hidden ? common_utils_1.performance.now() : undefined;
317
+ // keep track of last time page was visible for telemetry (on interactive clients only)
318
+ if (isDomAvailable && interactive) {
319
+ this.lastVisible = document.hidden ? client_utils_1.performance.now() : undefined;
286
320
  this.visibilityEventHandler = () => {
287
321
  if (document.hidden) {
288
- this.lastVisible = common_utils_1.performance.now();
322
+ this.lastVisible = client_utils_1.performance.now();
289
323
  }
290
324
  else {
291
325
  // settimeout so this will hopefully fire after disconnect event if being hidden caused it
@@ -302,7 +336,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
302
336
  * @internal
303
337
  */
304
338
  static async load(loadProps, createProps) {
305
- const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
339
+ const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } = loadProps;
306
340
  const container = new Container(createProps, loadProps);
307
341
  const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
308
342
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
@@ -310,26 +344,31 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
310
344
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
311
345
  // to return container, so ignore this value and use undefined for opsBeforeReturn
312
346
  const mode = pendingLocalState
313
- ? Object.assign(Object.assign({}, (loadMode !== null && loadMode !== void 0 ? loadMode : defaultMode)), { opsBeforeReturn: undefined }) : loadMode !== null && loadMode !== void 0 ? loadMode : defaultMode;
347
+ ? { ...(loadMode ?? defaultMode), opsBeforeReturn: undefined }
348
+ : loadMode ?? defaultMode;
314
349
  const onClosed = (err) => {
315
350
  // pre-0.58 error message: containerClosedWithoutErrorDuringLoad
316
- reject(err !== null && err !== void 0 ? err : new container_utils_1.GenericError("Container closed without error during load"));
351
+ reject(err ?? new telemetry_utils_1.GenericError("Container closed without error during load"));
317
352
  };
318
353
  container.on("closed", onClosed);
319
354
  container
320
- .load(version, mode, resolvedUrl, pendingLocalState)
355
+ .load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
321
356
  .finally(() => {
322
357
  container.removeListener("closed", onClosed);
323
358
  })
324
359
  .then((props) => {
325
- event.end(Object.assign(Object.assign({}, props), loadMode));
360
+ event.end({ ...props, ...loadMode });
326
361
  resolve(container);
327
362
  }, (error) => {
328
363
  const err = (0, telemetry_utils_1.normalizeError)(error);
329
364
  // Depending where error happens, we can be attempting to connect to web socket
330
365
  // and continuously retrying (consider offline mode)
331
366
  // Host has no container to close, so it's prudent to do it here
367
+ // Note: We could only dispose the container instead of just close but that would
368
+ // the telemetry where users sometimes search for ContainerClose event to look
369
+ // for load failures. So not removing this at this time.
332
370
  container.close(err);
371
+ container.dispose(err);
333
372
  onClosed(err);
334
373
  });
335
374
  }), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
@@ -366,19 +405,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
366
405
  }
367
406
  }
368
407
  get closed() {
369
- return (this._lifecycleState === "closing" ||
370
- this._lifecycleState === "closed" ||
371
- this._lifecycleState === "disposing" ||
372
- this._lifecycleState === "disposed");
408
+ return (this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed);
373
409
  }
374
- get storage() {
375
- return this.storageAdapter;
410
+ get disposed() {
411
+ return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
376
412
  }
377
- get context() {
378
- if (this._context === undefined) {
379
- throw new container_utils_1.GenericError("Attempted to access context before it was defined");
413
+ get runtime() {
414
+ if (this._runtime === undefined) {
415
+ throw new Error("Attempted to access runtime before it was defined");
380
416
  }
381
- return this._context;
417
+ return this._runtime;
382
418
  }
383
419
  get protocolHandler() {
384
420
  if (this._protocolHandler === undefined) {
@@ -393,7 +429,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
393
429
  return this;
394
430
  }
395
431
  get resolvedUrl() {
396
- var _a;
397
432
  /**
398
433
  * All attached containers will have a document service,
399
434
  * this is required, as attached containers are attached to
@@ -405,17 +440,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
405
440
  * is always the same as the containers, as we had to
406
441
  * obtain the resolved url, and then create the service from it.
407
442
  */
408
- return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
409
- }
410
- get loadedFromVersion() {
411
- return this._loadedFromVersion;
443
+ return this.service?.resolvedUrl;
412
444
  }
413
445
  get readOnlyInfo() {
414
446
  return this._deltaManager.readOnlyInfo;
415
447
  }
416
- get closeSignal() {
417
- return this._deltaManager.closeAbortController.signal;
418
- }
419
448
  /**
420
449
  * Tracks host requiring read-only mode.
421
450
  */
@@ -431,13 +460,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
431
460
  get connected() {
432
461
  return this.connectionStateHandler.connectionState === connectionState_1.ConnectionState.Connected;
433
462
  }
434
- /**
435
- * Service configuration details. If running in offline mode will be undefined otherwise will contain service
436
- * configuration details returned as part of the initial connection.
437
- */
438
- get serviceConfiguration() {
439
- return this._deltaManager.serviceConfiguration;
440
- }
441
463
  /**
442
464
  * The server provided id of the client.
443
465
  * Set once this.connected is true, otherwise undefined
@@ -445,21 +467,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
445
467
  get clientId() {
446
468
  return this._clientId;
447
469
  }
448
- /**
449
- * The server provided claims of the client.
450
- * Set once this.connected is true, otherwise undefined
451
- */
452
- get scopes() {
453
- return this._deltaManager.connectionManager.scopes;
454
- }
455
- get clientDetails() {
456
- return this._deltaManager.clientDetails;
457
- }
458
470
  get offlineLoadEnabled() {
459
- var _a, _b;
460
- const enabled = (_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : ((_b = this.options) === null || _b === void 0 ? void 0 : _b.enableOfflineLoad) === true;
471
+ const enabled = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
472
+ this.options?.enableOfflineLoad === true;
461
473
  // summarizer will not have any pending state we want to save
462
- return enabled && this.clientDetails.capabilities.interactive;
474
+ return enabled && this.deltaManager.clientDetails.capabilities.interactive;
463
475
  }
464
476
  /**
465
477
  * Get the code details that are currently specified for the container.
@@ -474,8 +486,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
474
486
  * loaded.
475
487
  */
476
488
  getLoadedCodeDetails() {
477
- var _a;
478
- return (_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails;
489
+ return this._loadedCodeDetails;
479
490
  }
480
491
  /**
481
492
  * Retrieves the audience associated with the document
@@ -495,33 +506,25 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
495
506
  * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
496
507
  */
497
508
  async getEntryPoint() {
498
- var _a, _b;
499
- // Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
500
- // allow it since they mean a kind of read-only state for the Container.
501
- // Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
502
- if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
503
- throw new container_utils_1.UsageError("The container is disposing or disposed");
509
+ if (this._disposed) {
510
+ throw new telemetry_utils_1.UsageError("The context is already disposed");
504
511
  }
505
- while (this._context === undefined) {
506
- await new Promise((resolve, reject) => {
507
- const contextChangedHandler = () => {
508
- resolve();
509
- this.off("disposed", disposedHandler);
510
- };
511
- const disposedHandler = (error) => {
512
- reject(error !== null && error !== void 0 ? error : "The Container is disposed");
513
- this.off("contextChanged", contextChangedHandler);
514
- };
515
- this.once("contextChanged", contextChangedHandler);
516
- this.once("disposed", disposedHandler);
517
- });
518
- // The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
519
- // should have set this._context; making sure.
520
- (0, common_utils_1.assert)(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
512
+ if (this._runtime !== undefined) {
513
+ return this._runtime.getEntryPoint?.();
521
514
  }
522
- // Disable lint rule for the sake of more complete stack traces
523
- // eslint-disable-next-line no-return-await
524
- return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
515
+ return new Promise((resolve, reject) => {
516
+ const runtimeInstantiatedHandler = () => {
517
+ (0, core_utils_1.assert)(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
518
+ resolve(this._runtime.getEntryPoint?.());
519
+ this._lifecycleEvents.off("disposed", disposedHandler);
520
+ };
521
+ const disposedHandler = () => {
522
+ reject(new Error("ContainerContext was disposed"));
523
+ this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
524
+ };
525
+ this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
526
+ this._lifecycleEvents.once("disposed", disposedHandler);
527
+ });
525
528
  }
526
529
  /**
527
530
  * Retrieves the quorum associated with the document
@@ -542,12 +545,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
542
545
  this.verifyClosed();
543
546
  }
544
547
  verifyClosed() {
545
- (0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
546
- (0, common_utils_1.assert)(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
548
+ (0, core_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
549
+ (0, core_utils_1.assert)(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
547
550
  }
548
551
  closeCore(error) {
549
- var _a;
550
- (0, common_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
552
+ (0, core_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
551
553
  try {
552
554
  // Ensure that we raise all key events even if one of these throws
553
555
  try {
@@ -562,7 +564,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
562
564
  : "generic",
563
565
  }, error);
564
566
  this._lifecycleState = "closing";
565
- (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
567
+ this._protocolHandler?.close();
566
568
  this.connectionStateHandler.dispose();
567
569
  }
568
570
  catch (exception) {
@@ -575,11 +577,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
575
577
  }
576
578
  finally {
577
579
  this._lifecycleState = "closed";
580
+ // There is no user for summarizer, so we need to ensure dispose is called
581
+ if (this.client.details.type === summarizerClientType) {
582
+ this.dispose(error);
583
+ }
578
584
  }
579
585
  }
580
586
  disposeCore(error) {
581
- var _a, _b, _c;
582
- (0, common_utils_1.assert)(!this._disposed, 0x54c /* Container already disposed */);
587
+ (0, core_utils_1.assert)(!this._disposed, 0x54c /* Container already disposed */);
583
588
  this._disposed = true;
584
589
  try {
585
590
  // Ensure that we raise all key events even if one of these throws
@@ -588,20 +593,22 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
588
593
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
589
594
  this.mc.logger.sendTelemetryEvent({
590
595
  eventName: "ContainerDispose",
591
- category: "generic",
596
+ // Only log error if container isn't closed
597
+ category: !this.closed && error !== undefined ? "error" : "generic",
592
598
  }, error);
593
599
  // ! Progressing from "closed" to "disposing" is not allowed
594
600
  if (this._lifecycleState !== "closed") {
595
601
  this._lifecycleState = "disposing";
596
602
  }
597
- (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
603
+ this._protocolHandler?.close();
598
604
  this.connectionStateHandler.dispose();
599
- (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
605
+ const maybeError = error !== undefined ? new Error(error.message) : undefined;
606
+ this._runtime?.dispose(maybeError);
600
607
  this.storageAdapter.dispose();
601
608
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
602
609
  // about file, like file being overwritten in storage, but client having stale local cache.
603
610
  // Driver need to ensure all caches are cleared on critical errors
604
- (_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
611
+ this.service?.dispose(error);
605
612
  }
606
613
  catch (exception) {
607
614
  this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
@@ -614,44 +621,60 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
614
621
  }
615
622
  finally {
616
623
  this._lifecycleState = "disposed";
624
+ this._lifecycleEvents.emit("disposed");
617
625
  }
618
626
  }
619
- closeAndGetPendingLocalState() {
627
+ async closeAndGetPendingLocalState() {
620
628
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
621
629
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
622
630
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
623
- const pendingState = this.getPendingLocalState();
631
+ this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
632
+ const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
624
633
  this.close();
625
634
  return pendingState;
626
635
  }
627
- getPendingLocalState() {
628
- if (!this.offlineLoadEnabled) {
629
- throw new container_utils_1.UsageError("Can't get pending local state unless offline load is enabled");
630
- }
631
- (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
632
- (0, common_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
633
- (0, common_utils_1.assert)(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
634
- (0, common_utils_1.assert)(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
635
- const pendingState = {
636
- pendingRuntimeState: this.context.getPendingLocalState(),
637
- baseSnapshot: this.baseSnapshot,
638
- snapshotBlobs: this.baseSnapshotBlobs,
639
- savedOps: this.savedOps,
640
- url: this.resolvedUrl.url,
641
- term: protocol_1.OnlyValidTermValue,
636
+ async getPendingLocalState() {
637
+ return this.getPendingLocalStateCore({ notifyImminentClosure: false });
638
+ }
639
+ async getPendingLocalStateCore(props) {
640
+ return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, {
641
+ eventName: "getPendingLocalState",
642
+ notifyImminentClosure: props.notifyImminentClosure,
643
+ savedOpsSize: this.savedOps.length,
642
644
  clientId: this.clientId,
643
- };
644
- this.mc.logger.sendTelemetryEvent({ eventName: "GetPendingLocalState" });
645
- return JSON.stringify(pendingState);
645
+ }, async () => {
646
+ if (!this.offlineLoadEnabled) {
647
+ throw new telemetry_utils_1.UsageError("Can't get pending local state unless offline load is enabled");
648
+ }
649
+ if (this.closed || this._disposed) {
650
+ throw new telemetry_utils_1.UsageError("Pending state cannot be retried if the container is closed or disposed");
651
+ }
652
+ (0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
653
+ (0, core_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
654
+ (0, core_utils_1.assert)(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
655
+ (0, core_utils_1.assert)(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
656
+ const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
657
+ const pendingState = {
658
+ pendingRuntimeState,
659
+ baseSnapshot: this.baseSnapshot,
660
+ snapshotBlobs: this.baseSnapshotBlobs,
661
+ savedOps: this.savedOps,
662
+ url: this.resolvedUrl.url,
663
+ term: protocol_1.OnlyValidTermValue,
664
+ // no need to save this if there is no pending runtime state
665
+ clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
666
+ };
667
+ return JSON.stringify(pendingState);
668
+ });
646
669
  }
647
670
  get attachState() {
648
671
  return this._attachState;
649
672
  }
650
673
  serialize() {
651
- (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
652
- const appSummary = this.context.createSummary();
674
+ (0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
675
+ const appSummary = this.runtime.createSummary();
653
676
  const protocolSummary = this.captureProtocolSummary();
654
- const combinedSummary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
677
+ const combinedSummary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
655
678
  if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
656
679
  combinedSummary.tree[".hasAttachmentBlobs"] = {
657
680
  type: protocol_definitions_1.SummaryType.Blob,
@@ -660,32 +683,32 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
660
683
  }
661
684
  return JSON.stringify(combinedSummary);
662
685
  }
663
- async attach(request) {
686
+ async attach(request, attachProps) {
664
687
  await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
665
- var _a;
666
688
  if (this._lifecycleState !== "loaded") {
667
689
  // pre-0.58 error message: containerNotValidForAttach
668
- throw new container_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
690
+ throw new telemetry_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
669
691
  }
670
692
  // If container is already attached or attach is in progress, throw an error.
671
- (0, common_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
693
+ (0, core_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
672
694
  this.attachStarted = true;
673
695
  // If attachment blobs were uploaded in detached state we will go through a different attach flow
674
696
  const hasAttachmentBlobs = this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
675
697
  try {
676
- (0, common_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
698
+ (0, core_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
677
699
  let summary;
678
700
  if (!hasAttachmentBlobs) {
679
701
  // Get the document state post attach - possibly can just call attach but we need to change the
680
702
  // semantics around what the attach means as far as async code goes.
681
- const appSummary = this.context.createSummary();
703
+ const appSummary = this.runtime.createSummary();
682
704
  const protocolSummary = this.captureProtocolSummary();
683
- summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
705
+ summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
684
706
  // Set the state as attaching as we are starting the process of attaching container.
685
707
  // This should be fired after taking the summary because it is the place where we are
686
708
  // starting to attach the container to storage.
687
709
  // Also, this should only be fired in detached container.
688
710
  this._attachState = container_definitions_1.AttachState.Attaching;
711
+ this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
689
712
  this.emit("attaching");
690
713
  if (this.offlineLoadEnabled) {
691
714
  const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
@@ -697,16 +720,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
697
720
  // Actually go and create the resolved document
698
721
  if (this.service === undefined) {
699
722
  const createNewResolvedUrl = await this.urlResolver.resolve(request);
700
- (0, common_utils_1.assert)(this.client.details.type !== summarizerClientType &&
723
+ (0, core_utils_1.assert)(this.client.details.type !== summarizerClientType &&
701
724
  createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
702
725
  this.service = await (0, driver_utils_1.runWithRetry)(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
703
- cancel: this.closeSignal,
726
+ cancel: this._deltaManager.closeAbortController.signal,
704
727
  });
705
728
  }
706
729
  await this.storageAdapter.connectToService(this.service);
707
730
  if (hasAttachmentBlobs) {
708
731
  // upload blobs to storage
709
- (0, common_utils_1.assert)(!!this.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
732
+ (0, core_utils_1.assert)(!!this.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
710
733
  // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
711
734
  // support blob handles that only know about the local IDs
712
735
  const redirectTable = new Map();
@@ -722,10 +745,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
722
745
  }
723
746
  }
724
747
  // take summary and upload
725
- const appSummary = this.context.createSummary(redirectTable);
748
+ const appSummary = this.runtime.createSummary(redirectTable);
726
749
  const protocolSummary = this.captureProtocolSummary();
727
- summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
750
+ summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
728
751
  this._attachState = container_definitions_1.AttachState.Attaching;
752
+ this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
729
753
  this.emit("attaching");
730
754
  if (this.offlineLoadEnabled) {
731
755
  const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
@@ -740,32 +764,33 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
740
764
  });
741
765
  }
742
766
  this._attachState = container_definitions_1.AttachState.Attached;
767
+ this.runtime.setAttachState(container_definitions_1.AttachState.Attached);
743
768
  this.emit("attached");
744
769
  if (!this.closed) {
745
- this.resumeInternal({
770
+ this.handleDeltaConnectionArg({
746
771
  fetchOpsFromStorage: false,
747
- reason: "createDetached",
748
- });
772
+ reason: { text: "createDetached" },
773
+ }, attachProps?.deltaConnection);
749
774
  }
750
775
  }
751
776
  catch (error) {
752
777
  // add resolved URL on error object so that host has the ability to find this document and delete it
753
778
  const newError = (0, telemetry_utils_1.normalizeError)(error);
754
- newError.addTelemetryProperties({ resolvedUrl: (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.url });
779
+ newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
755
780
  this.close(newError);
756
781
  throw newError;
757
782
  }
758
783
  }, { start: true, end: true, cancel: "generic" });
759
784
  }
760
785
  async request(path) {
761
- return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
786
+ return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
762
787
  }
763
- setAutoReconnectInternal(mode) {
788
+ setAutoReconnectInternal(mode, reason) {
764
789
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
765
790
  if (currentMode === mode) {
766
791
  return;
767
792
  }
768
- const now = common_utils_1.performance.now();
793
+ const now = client_utils_1.performance.now();
769
794
  const duration = now - this.setAutoReconnectTime;
770
795
  this.setAutoReconnectTime = now;
771
796
  this.mc.logger.sendTelemetryEvent({
@@ -774,47 +799,50 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
774
799
  connectionState: connectionState_1.ConnectionState[this.connectionState],
775
800
  duration,
776
801
  });
777
- this._deltaManager.connectionManager.setAutoReconnect(mode);
802
+ this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
778
803
  }
779
804
  connect() {
780
805
  if (this.closed) {
781
- throw new container_utils_1.UsageError(`The Container is closed and cannot be connected`);
806
+ throw new telemetry_utils_1.UsageError(`The Container is closed and cannot be connected`);
782
807
  }
783
808
  else if (this._attachState !== container_definitions_1.AttachState.Attached) {
784
- throw new container_utils_1.UsageError(`The Container is not attached and cannot be connected`);
809
+ throw new telemetry_utils_1.UsageError(`The Container is not attached and cannot be connected`);
785
810
  }
786
811
  else if (!this.connected) {
787
812
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
788
813
  // If there is gap, we will learn about it once connected, but the gap should be small (if any),
789
814
  // assuming that connect() is called quickly after initial container boot.
790
- this.connectInternal({ reason: "DocumentConnect", fetchOpsFromStorage: false });
815
+ this.connectInternal({
816
+ reason: { text: "DocumentConnect" },
817
+ fetchOpsFromStorage: false,
818
+ });
791
819
  }
792
820
  }
793
821
  connectInternal(args) {
794
- (0, common_utils_1.assert)(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
795
- (0, common_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Attached, 0x2c6 /* "Attempting to connect() a container that is not attached" */);
822
+ (0, core_utils_1.assert)(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
823
+ (0, core_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Attached, 0x2c6 /* "Attempting to connect() a container that is not attached" */);
796
824
  // Resume processing ops and connect to delta stream
797
825
  this.resumeInternal(args);
798
826
  // Set Auto Reconnect Mode
799
827
  const mode = contracts_1.ReconnectMode.Enabled;
800
- this.setAutoReconnectInternal(mode);
828
+ this.setAutoReconnectInternal(mode, args.reason);
801
829
  }
802
830
  disconnect() {
803
831
  if (this.closed) {
804
- throw new container_utils_1.UsageError(`The Container is closed and cannot be disconnected`);
832
+ throw new telemetry_utils_1.UsageError(`The Container is closed and cannot be disconnected`);
805
833
  }
806
834
  else {
807
- this.disconnectInternal();
835
+ this.disconnectInternal({ text: "DocumentDisconnect" });
808
836
  }
809
837
  }
810
- disconnectInternal() {
811
- (0, common_utils_1.assert)(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
838
+ disconnectInternal(reason) {
839
+ (0, core_utils_1.assert)(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
812
840
  // Set Auto Reconnect Mode
813
841
  const mode = contracts_1.ReconnectMode.Disabled;
814
- this.setAutoReconnectInternal(mode);
842
+ this.setAutoReconnectInternal(mode, reason);
815
843
  }
816
844
  resumeInternal(args) {
817
- (0, common_utils_1.assert)(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
845
+ (0, core_utils_1.assert)(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
818
846
  // Resume processing ops
819
847
  if (this.inboundQueuePausedFromInit) {
820
848
  this.inboundQueuePausedFromInit = false;
@@ -824,13 +852,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
824
852
  // Ensure connection to web socket
825
853
  this.connectToDeltaStream(args);
826
854
  }
827
- async getAbsoluteUrl(relativeUrl) {
828
- var _a;
829
- if (this.resolvedUrl === undefined) {
830
- return undefined;
831
- }
832
- return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)((_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails));
833
- }
834
855
  async proposeCodeDetails(codeDetails) {
835
856
  if (!(0, container_definitions_1.isFluidCodeDetails)(codeDetails)) {
836
857
  throw new Error("Provided codeDetails are not IFluidCodeDetails");
@@ -852,15 +873,46 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
852
873
  this.deltaManager.inbound.pause(),
853
874
  this.deltaManager.inboundSignal.pause(),
854
875
  ]);
855
- if ((await this.context.satisfies(codeDetails)) === true) {
876
+ if ((await this.satisfies(codeDetails)) === true) {
856
877
  this.deltaManager.inbound.resume();
857
878
  this.deltaManager.inboundSignal.resume();
858
879
  return;
859
880
  }
860
881
  // pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
861
- const error = new container_utils_1.GenericError("Existing context does not satisfy incoming proposal");
882
+ const error = new telemetry_utils_1.GenericError("Existing context does not satisfy incoming proposal");
862
883
  this.close(error);
863
884
  }
885
+ /**
886
+ * Determines if the currently loaded module satisfies the incoming constraint code details
887
+ */
888
+ async satisfies(constraintCodeDetails) {
889
+ // If we have no module, it can't satisfy anything.
890
+ if (this._loadedModule === undefined) {
891
+ return false;
892
+ }
893
+ const comparers = [];
894
+ const maybeCompareCodeLoader = this.codeLoader;
895
+ if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
896
+ comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
897
+ }
898
+ const maybeCompareExport = this._loadedModule?.module.fluidExport;
899
+ if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
900
+ comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
901
+ }
902
+ // If there are no comparers, then it's impossible to know if the currently loaded package satisfies
903
+ // the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
904
+ // rather than potentially running with incompatible code.
905
+ if (comparers.length === 0) {
906
+ return false;
907
+ }
908
+ for (const comparer of comparers) {
909
+ const satisfies = await comparer.satisfies(this._loadedModule?.details, constraintCodeDetails);
910
+ if (satisfies === false) {
911
+ return false;
912
+ }
913
+ }
914
+ return true;
915
+ }
864
916
  async getVersion(version) {
865
917
  const versions = await this.storageAdapter.getVersions(version, 1);
866
918
  return versions[0];
@@ -877,8 +929,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
877
929
  *
878
930
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
879
931
  */
880
- async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
881
- var _a;
932
+ async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
933
+ const timings = { phase1: client_utils_1.performance.now() };
882
934
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
883
935
  // Ideally we always connect as "read" by default.
884
936
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
@@ -890,7 +942,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
890
942
  // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
891
943
  // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
892
944
  const connectionArgs = {
893
- reason: "DocumentOpen",
945
+ reason: { text: "DocumentOpen" },
894
946
  mode: "write",
895
947
  fetchOpsFromStorage: false,
896
948
  };
@@ -909,6 +961,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
909
961
  });
910
962
  }
911
963
  this._attachState = container_definitions_1.AttachState.Attached;
964
+ timings.phase2 = client_utils_1.performance.now();
912
965
  // Fetch specified snapshot.
913
966
  const { snapshot, versionId } = pendingLocalState === undefined
914
967
  ? await this.fetchSnapshotTree(specifiedVersion)
@@ -918,18 +971,60 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
918
971
  this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
919
972
  }
920
973
  else {
921
- (0, common_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
974
+ (0, core_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
922
975
  if (this.offlineLoadEnabled) {
923
976
  this.baseSnapshot = snapshot;
924
977
  // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
925
- this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storage);
978
+ this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storageAdapter);
926
979
  }
927
980
  }
928
981
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
929
982
  // If we saved ops, we will replay them and don't need DeltaManager to fetch them
930
- const sequenceNumber = (_a = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.savedOps[pendingLocalState.savedOps.length - 1]) === null || _a === void 0 ? void 0 : _a.sequenceNumber;
931
- const dmAttributes = sequenceNumber !== undefined ? Object.assign(Object.assign({}, attributes), { sequenceNumber }) : attributes;
983
+ const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
984
+ const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
932
985
  let opsBeforeReturnP;
986
+ if (loadMode.pauseAfterLoad === true) {
987
+ // If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
988
+ if (loadMode.opsBeforeReturn === "sequenceNumber") {
989
+ (0, core_utils_1.assert)(loadToSequenceNumber !== undefined, 0x727 /* sequenceNumber should be defined */);
990
+ // Note: It is possible that we think the latest snapshot is newer than the specified sequence number
991
+ // due to saved ops that may be replayed after the snapshot.
992
+ // https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
993
+ if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
994
+ throw new Error("Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.");
995
+ }
996
+ }
997
+ // Force readonly mode - this will ensure we don't receive an error for the lack of join op
998
+ this.forceReadonly(true);
999
+ // We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
1000
+ const opHandler = () => {
1001
+ if (loadToSequenceNumber === undefined) {
1002
+ // If there is no specified sequence number, pause after the inbound queue is empty.
1003
+ if (this.deltaManager.inbound.length !== 0) {
1004
+ return;
1005
+ }
1006
+ }
1007
+ else {
1008
+ // If there is a specified sequence number, keep processing until we reach it.
1009
+ if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
1010
+ return;
1011
+ }
1012
+ }
1013
+ // Pause op processing once we have processed the desired number of ops.
1014
+ void this.deltaManager.inbound.pause();
1015
+ void this.deltaManager.outbound.pause();
1016
+ this.off("op", opHandler);
1017
+ };
1018
+ if ((loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
1019
+ this.deltaManager.lastSequenceNumber === loadToSequenceNumber) {
1020
+ // If we have already reached the desired sequence number, call opHandler() to pause immediately.
1021
+ opHandler();
1022
+ }
1023
+ else {
1024
+ // If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
1025
+ this.on("op", opHandler);
1026
+ }
1027
+ }
933
1028
  // Attach op handlers to finish initialization and be able to start processing ops
934
1029
  // Kick off any ops fetching if required.
935
1030
  switch (loadMode.opsBeforeReturn) {
@@ -938,32 +1033,30 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
938
1033
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
939
1034
  this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
940
1035
  break;
1036
+ case "sequenceNumber":
941
1037
  case "cached":
942
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
943
- break;
944
1038
  case "all":
945
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
1039
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, loadMode.opsBeforeReturn);
946
1040
  break;
947
1041
  default:
948
- (0, common_utils_1.unreachableCase)(loadMode.opsBeforeReturn);
1042
+ (0, core_utils_1.unreachableCase)(loadMode.opsBeforeReturn);
949
1043
  }
950
1044
  // ...load in the existing quorum
951
1045
  // Initialize the protocol handler
952
1046
  await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1047
+ timings.phase3 = client_utils_1.performance.now();
953
1048
  const codeDetails = this.getCodeDetailsFromQuorum();
954
- await this.instantiateContext(true, // existing
955
- codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
1049
+ await this.instantiateRuntime(codeDetails, snapshot,
1050
+ // give runtime a dummy value so it knows we're loading from a stash blob
1051
+ pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined);
956
1052
  // replay saved ops
957
1053
  if (pendingLocalState) {
958
1054
  for (const message of pendingLocalState.savedOps) {
959
1055
  this.processRemoteMessage(message);
960
1056
  // allow runtime to apply stashed ops at this op's sequence number
961
- await this.context.notifyOpReplay(message);
1057
+ await this.runtime.notifyOpReplay?.(message);
962
1058
  }
963
1059
  pendingLocalState.savedOps = [];
964
- // now set clientId to stashed clientId so live ops are correctly processed as local
965
- (0, common_utils_1.assert)(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
966
- this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
967
1060
  }
968
1061
  // We might have hit some failure that did not manifest itself in exception in this flow,
969
1062
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
@@ -975,24 +1068,20 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
975
1068
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
976
1069
  this._deltaManager.inbound.pause();
977
1070
  }
978
- switch (loadMode.deltaConnection) {
979
- case undefined:
980
- if (pendingLocalState) {
981
- // connect to delta stream now since we did not before
982
- this.connectToDeltaStream(connectionArgs);
1071
+ this.handleDeltaConnectionArg(connectionArgs, loadMode.deltaConnection, pendingLocalState !== undefined);
1072
+ }
1073
+ // If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
1074
+ if (loadToSequenceNumber !== undefined &&
1075
+ this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
1076
+ await new Promise((resolve, reject) => {
1077
+ const opHandler = (message) => {
1078
+ if (message.sequenceNumber >= loadToSequenceNumber) {
1079
+ resolve();
1080
+ this.off("op", opHandler);
983
1081
  }
984
- // intentional fallthrough
985
- case "delayed":
986
- (0, common_utils_1.assert)(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
987
- this.inboundQueuePausedFromInit = false;
988
- this._deltaManager.inbound.resume();
989
- this._deltaManager.inboundSignal.resume();
990
- break;
991
- case "none":
992
- break;
993
- default:
994
- (0, common_utils_1.unreachableCase)(loadMode.deltaConnection);
995
- }
1082
+ };
1083
+ this.on("op", opHandler);
1084
+ });
996
1085
  }
997
1086
  // Safety net: static version of Container.load() should have learned about it through "closed" handler.
998
1087
  // But if that did not happen for some reason, fail load for sure.
@@ -1004,6 +1093,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1004
1093
  }
1005
1094
  // Internal context is fully loaded at this point
1006
1095
  this.setLoaded();
1096
+ timings.end = client_utils_1.performance.now();
1097
+ this.subLogger.sendTelemetryEvent({
1098
+ eventName: "LoadStagesTimings",
1099
+ details: JSON.stringify(timings),
1100
+ }, undefined, core_interfaces_1.LogLevel.verbose);
1007
1101
  return {
1008
1102
  sequenceNumber: attributes.sequenceNumber,
1009
1103
  version: versionId,
@@ -1011,7 +1105,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1011
1105
  dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
1012
1106
  };
1013
1107
  }
1014
- async createDetached(source) {
1108
+ async createDetached(codeDetails) {
1015
1109
  const attributes = {
1016
1110
  sequenceNumber: detachedContainerRefSeqNumber,
1017
1111
  term: protocol_1.OnlyValidTermValue,
@@ -1019,19 +1113,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1019
1113
  };
1020
1114
  await this.attachDeltaManagerOpHandler(attributes);
1021
1115
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1022
- const qValues = (0, quorum_1.initQuorumValuesFromCodeDetails)(source);
1116
+ const qValues = (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails);
1023
1117
  this.initializeProtocolState(attributes, {
1024
1118
  members: [],
1025
1119
  proposals: [],
1026
1120
  values: qValues,
1027
1121
  });
1028
- // The load context - given we seeded the quorum - will be great
1029
- await this.instantiateContextDetached(false);
1122
+ await this.instantiateRuntime(codeDetails, undefined);
1030
1123
  this.setLoaded();
1031
1124
  }
1032
1125
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
1033
1126
  if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
1034
- (0, common_utils_1.assert)(!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
1127
+ (0, core_utils_1.assert)(!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
1035
1128
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
1036
1129
  }
1037
1130
  const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
@@ -1041,14 +1134,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1041
1134
  // Initialize the protocol handler
1042
1135
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
1043
1136
  const qValues = await (0, driver_utils_1.readAndParse)(this.storageAdapter, baseTree.blobs.quorumValues);
1044
- const codeDetails = (0, quorum_1.getCodeDetailsFromQuorumValues)(qValues);
1045
1137
  this.initializeProtocolState(attributes, {
1046
1138
  members: [],
1047
1139
  proposals: [],
1048
- values: codeDetails !== undefined ? (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails) : [],
1140
+ values: qValues,
1049
1141
  });
1050
- await this.instantiateContextDetached(true, // existing
1051
- snapshotTree);
1142
+ const codeDetails = this.getCodeDetailsFromQuorum();
1143
+ await this.instantiateRuntime(codeDetails, snapshotTree);
1052
1144
  this.setLoaded();
1053
1145
  }
1054
1146
  async getDocumentAttributes(storage, tree) {
@@ -1085,7 +1177,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1085
1177
  }
1086
1178
  initializeProtocolState(attributes, quorumSnapshot) {
1087
1179
  const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, JSON.stringify({ key, value })));
1088
- const protocolLogger = telemetry_utils_1.ChildLogger.create(this.subLogger, "ProtocolHandler");
1180
+ const protocolLogger = (0, telemetry_utils_1.createChildLogger)({
1181
+ logger: this.subLogger,
1182
+ namespace: "ProtocolHandler",
1183
+ });
1089
1184
  protocol.quorum.on("error", (error) => {
1090
1185
  protocolLogger.sendErrorEvent(error);
1091
1186
  });
@@ -1147,8 +1242,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1147
1242
  return pkg;
1148
1243
  }
1149
1244
  get client() {
1150
- var _a;
1151
- const client = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.client) !== undefined
1245
+ const client = this.options?.client !== undefined
1152
1246
  ? this.options.client
1153
1247
  : {
1154
1248
  details: {
@@ -1179,14 +1273,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1179
1273
  }
1180
1274
  createDeltaManager() {
1181
1275
  const serviceProvider = () => this.service;
1182
- const deltaManager = new deltaManager_1.DeltaManager(serviceProvider, telemetry_utils_1.ChildLogger.create(this.subLogger, "DeltaManager"), () => this.activeConnection(), (props) => new connectionManager_1.ConnectionManager(serviceProvider, () => this.isDirty, this.client, this._canReconnect, telemetry_utils_1.ChildLogger.create(this.subLogger, "ConnectionManager"), props));
1276
+ const deltaManager = new deltaManager_1.DeltaManager(serviceProvider, (0, telemetry_utils_1.createChildLogger)({ logger: this.subLogger, namespace: "DeltaManager" }), () => this.activeConnection(), (props) => new connectionManager_1.ConnectionManager(serviceProvider, () => this.isDirty, this.client, this._canReconnect, (0, telemetry_utils_1.createChildLogger)({ logger: this.subLogger, namespace: "ConnectionManager" }), props));
1183
1277
  // Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
1184
1278
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1185
1279
  deltaManager.inbound.pause();
1186
1280
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1187
1281
  deltaManager.inboundSignal.pause();
1188
1282
  deltaManager.on("connect", (details, _opsBehind) => {
1189
- (0, common_utils_1.assert)(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
1283
+ (0, core_utils_1.assert)(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
1190
1284
  this.connectionStateHandler.receivedConnectEvent(details);
1191
1285
  });
1192
1286
  deltaManager.on("establishingConnection", (reason) => {
@@ -1195,11 +1289,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1195
1289
  deltaManager.on("cancelEstablishingConnection", (reason) => {
1196
1290
  this.connectionStateHandler.cancelEstablishingConnection(reason);
1197
1291
  });
1198
- deltaManager.on("disconnect", (reason, error) => {
1199
- var _a;
1200
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1292
+ deltaManager.on("disconnect", (reason) => {
1293
+ this.noopHeuristic?.notifyDisconnect();
1201
1294
  if (!this.closed) {
1202
- this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1295
+ this.connectionStateHandler.receivedDisconnectEvent(reason);
1203
1296
  }
1204
1297
  });
1205
1298
  deltaManager.on("throttled", (warning) => {
@@ -1231,10 +1324,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1231
1324
  },
1232
1325
  }, prefetchType);
1233
1326
  }
1234
- logConnectionStateChangeTelemetry(value, oldState, reason, error) {
1235
- var _a;
1327
+ logConnectionStateChangeTelemetry(value, oldState, reason) {
1236
1328
  // Log actual event
1237
- const time = common_utils_1.performance.now();
1329
+ const time = client_utils_1.performance.now();
1238
1330
  this.connectionTransitionTimes[value] = time;
1239
1331
  const duration = time - this.connectionTransitionTimes[oldState];
1240
1332
  let durationFromDisconnected;
@@ -1249,30 +1341,44 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1249
1341
  if (value === connectionState_1.ConnectionState.Connected) {
1250
1342
  durationFromDisconnected =
1251
1343
  time - this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected];
1252
- durationFromDisconnected = telemetry_utils_1.TelemetryLogger.formatTick(durationFromDisconnected);
1344
+ durationFromDisconnected = (0, telemetry_utils_1.formatTick)(durationFromDisconnected);
1253
1345
  }
1254
1346
  else if (value === connectionState_1.ConnectionState.CatchingUp) {
1255
1347
  // This info is of most interesting while Catching Up.
1256
1348
  checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
1257
- if (this.deltaManager.hasCheckpointSequenceNumber) {
1349
+ // Need to check that we have already loaded and fetched the snapshot.
1350
+ if (this.deltaManager.hasCheckpointSequenceNumber &&
1351
+ this._lifecycleState === "loaded") {
1258
1352
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1259
1353
  }
1260
1354
  }
1261
1355
  connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1262
1356
  }
1263
- this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${connectionState_1.ConnectionState[value]}`, from: connectionState_1.ConnectionState[oldState], duration,
1357
+ this.mc.logger.sendPerformanceEvent({
1358
+ eventName: `ConnectionStateChange_${connectionState_1.ConnectionState[value]}`,
1359
+ from: connectionState_1.ConnectionState[oldState],
1360
+ duration,
1264
1361
  durationFromDisconnected,
1265
- reason,
1266
- connectionInitiationReason, pendingClientId: this.connectionStateHandler.pendingClientId, clientId: this.clientId, autoReconnect,
1267
- opsBehind, online: driver_utils_1.OnlineStatus[(0, driver_utils_1.isOnline)()], lastVisible: this.lastVisible !== undefined
1268
- ? common_utils_1.performance.now() - this.lastVisible
1269
- : undefined, checkpointSequenceNumber, quorumSize: (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum.getMembers().size }, this._deltaManager.connectionProps), error);
1362
+ reason: reason?.text,
1363
+ connectionInitiationReason,
1364
+ pendingClientId: this.connectionStateHandler.pendingClientId,
1365
+ clientId: this.clientId,
1366
+ autoReconnect,
1367
+ opsBehind,
1368
+ online: driver_utils_1.OnlineStatus[(0, driver_utils_1.isOnline)()],
1369
+ lastVisible: this.lastVisible !== undefined
1370
+ ? client_utils_1.performance.now() - this.lastVisible
1371
+ : undefined,
1372
+ checkpointSequenceNumber,
1373
+ quorumSize: this._protocolHandler?.quorum.getMembers().size,
1374
+ isDirty: this.isDirty,
1375
+ ...this._deltaManager.connectionProps,
1376
+ }, reason?.error);
1270
1377
  if (value === connectionState_1.ConnectionState.Connected) {
1271
1378
  this.firstConnection = false;
1272
1379
  }
1273
1380
  }
1274
1381
  propagateConnectionState(initialTransition, disconnectedReason) {
1275
- var _a;
1276
1382
  // When container loaded, we want to propagate initial connection state.
1277
1383
  // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1278
1384
  // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
@@ -1282,22 +1388,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1282
1388
  return;
1283
1389
  }
1284
1390
  const state = this.connectionState === connectionState_1.ConnectionState.Connected;
1285
- const logOpsOnReconnect = this.connectionState === connectionState_1.ConnectionState.Connected &&
1286
- !this.firstConnection &&
1287
- this.connectionMode === "write";
1288
- if (logOpsOnReconnect) {
1289
- this.messageCountAfterDisconnection = 0;
1290
- }
1291
1391
  // Both protocol and context should not be undefined if we got so far.
1292
- this.setContextConnectedState(state, (_a = this.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1392
+ this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
1293
1393
  this.protocolHandler.setConnectionState(state, this.clientId);
1294
- (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason);
1295
- if (logOpsOnReconnect) {
1296
- this.mc.logger.sendTelemetryEvent({
1297
- eventName: "OpsSentOnReconnect",
1298
- count: this.messageCountAfterDisconnection,
1299
- });
1300
- }
1394
+ (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
1301
1395
  }
1302
1396
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1303
1397
  submitContainerMessage(type, contents, batch, metadata) {
@@ -1307,7 +1401,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1307
1401
  case protocol_definitions_1.MessageType.Summarize:
1308
1402
  return this.submitSummaryMessage(contents);
1309
1403
  default: {
1310
- const newError = new container_utils_1.GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type });
1404
+ const newError = new telemetry_utils_1.GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type });
1311
1405
  this.close(newError);
1312
1406
  return -1;
1313
1407
  }
@@ -1335,13 +1429,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1335
1429
  return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
1336
1430
  }
1337
1431
  submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
1338
- var _a;
1339
1432
  if (this.connectionState !== connectionState_1.ConnectionState.Connected) {
1340
1433
  this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
1341
1434
  return -1;
1342
1435
  }
1343
- this.messageCountAfterDisconnection += 1;
1344
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1436
+ this.noopHeuristic?.notifyMessageSent();
1345
1437
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1346
1438
  }
1347
1439
  processRemoteMessage(message) {
@@ -1352,21 +1444,31 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1352
1444
  // Allow the protocol handler to process the message
1353
1445
  const result = this.protocolHandler.processMessage(message, local);
1354
1446
  // Forward messages to the loaded runtime for processing
1355
- this.context.process(message, local);
1447
+ this.runtime.process(message, local);
1356
1448
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1357
1449
  if (this.activeConnection()) {
1358
- if (this.collabWindowTracker === undefined) {
1450
+ if (this.noopHeuristic === undefined) {
1451
+ const serviceConfiguration = this.deltaManager.serviceConfiguration;
1359
1452
  // Note that config from first connection will be used for this container's lifetime.
1360
1453
  // That means that if relay service changes settings, such changes will impact only newly booted
1361
1454
  // clients.
1362
1455
  // All existing will continue to use settings they got earlier.
1363
- (0, common_utils_1.assert)(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1364
- this.collabWindowTracker = new collabWindowTracker_1.CollabWindowTracker((type) => {
1365
- (0, common_utils_1.assert)(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1366
- this.submitMessage(type);
1367
- }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1456
+ (0, core_utils_1.assert)(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1457
+ this.noopHeuristic = new noopHeuristic_1.NoopHeuristic(serviceConfiguration.noopTimeFrequency, serviceConfiguration.noopCountFrequency);
1458
+ this.noopHeuristic.on("wantsNoop", () => {
1459
+ // On disconnect we notify the heuristic which should prevent it from wanting a noop.
1460
+ // Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
1461
+ // running the microtask that the heuristic queued in response.
1462
+ (0, core_utils_1.assert)(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
1463
+ this.submitMessage(protocol_definitions_1.MessageType.NoOp);
1464
+ });
1465
+ }
1466
+ this.noopHeuristic.notifyMessageProcessed(message);
1467
+ // The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
1468
+ if (result.immediateNoOp === true) {
1469
+ // ADO:1385: Remove cast and use MessageType once definition changes propagate
1470
+ this.submitMessage(driver_utils_1.MessageType2.Accept);
1368
1471
  }
1369
- this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1370
1472
  }
1371
1473
  this.emit("op", message);
1372
1474
  }
@@ -1375,12 +1477,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1375
1477
  }
1376
1478
  processSignal(message) {
1377
1479
  // No clientId indicates a system signal message.
1378
- if (message.clientId === null) {
1480
+ if ((0, protocol_1.protocolHandlerShouldProcessSignal)(message)) {
1379
1481
  this.protocolHandler.processSignal(message);
1380
1482
  }
1381
1483
  else {
1382
1484
  const local = this.clientId === message.clientId;
1383
- this.context.processSignal(message, local);
1485
+ this.runtime.processSignal(message, local);
1384
1486
  }
1385
1487
  }
1386
1488
  /**
@@ -1389,8 +1491,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1389
1491
  * @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
1390
1492
  */
1391
1493
  async fetchSnapshotTree(specifiedVersion) {
1392
- var _a;
1393
- const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
1494
+ const version = await this.getVersion(specifiedVersion ?? null);
1394
1495
  if (version === undefined && specifiedVersion !== undefined) {
1395
1496
  // We should have a defined version to load from if specified version requested
1396
1497
  this.mc.logger.sendErrorEvent({
@@ -1399,35 +1500,38 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1399
1500
  });
1400
1501
  }
1401
1502
  this._loadedFromVersion = version;
1402
- const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
1503
+ const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
1403
1504
  if (snapshot === undefined && version !== undefined) {
1404
1505
  this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
1405
1506
  }
1406
- return { snapshot, versionId: version === null || version === void 0 ? void 0 : version.id };
1407
- }
1408
- async instantiateContextDetached(existing, snapshot) {
1409
- const codeDetails = this.getCodeDetailsFromQuorum();
1410
- if (codeDetails === undefined) {
1411
- throw new Error("pkg should be provided in create flow!!");
1412
- }
1413
- await this.instantiateContext(existing, codeDetails, snapshot);
1507
+ return { snapshot, versionId: version?.id };
1414
1508
  }
1415
- async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1416
- var _a;
1417
- (0, common_utils_1.assert)(((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing context not disposed" */);
1509
+ async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
1510
+ (0, core_utils_1.assert)(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
1418
1511
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1419
1512
  // are set. Global requests will still go directly to the loader
1420
1513
  const maybeLoader = this.scope;
1421
1514
  const loader = new loader_1.RelativeLoader(this, maybeLoader.ILoader);
1422
- this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => this.dispose(error), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1423
- this.emit("contextChanged", codeDetails);
1424
- }
1425
- updateDirtyContainerState(dirty) {
1426
- if (this._dirtyContainer === dirty) {
1427
- return;
1515
+ const loadCodeResult = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "CodeLoad" }, async () => this.codeLoader.load(codeDetails));
1516
+ this._loadedModule = {
1517
+ module: loadCodeResult.module,
1518
+ // An older interface ICodeLoader could return an IFluidModule which didn't have details.
1519
+ // If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
1520
+ // TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
1521
+ details: loadCodeResult.details ?? codeDetails,
1522
+ };
1523
+ const fluidExport = this._loadedModule.module.fluidExport;
1524
+ const runtimeFactory = fluidExport?.IRuntimeFactory;
1525
+ if (runtimeFactory === undefined) {
1526
+ throw new Error(packageNotFactoryError);
1428
1527
  }
1429
- this._dirtyContainer = dirty;
1430
- this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
1528
+ const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
1529
+ this.protocolHandler.quorum.get("code2"));
1530
+ const existing = snapshot !== undefined;
1531
+ const context = new containerContext_1.ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, this.protocolHandler.audience, loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => this.resolvedUrl?.id, () => this.clientId, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
1532
+ this._runtime = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1533
+ this._lifecycleEvents.emit("runtimeInstantiated");
1534
+ this._loadedCodeDetails = codeDetails;
1431
1535
  }
1432
1536
  /**
1433
1537
  * Set the connected state of the ContainerContext
@@ -1436,18 +1540,36 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1436
1540
  * @param readonly - Is the container in readonly mode?
1437
1541
  */
1438
1542
  setContextConnectedState(state, readonly) {
1439
- var _a;
1440
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1543
+ if (this._runtime?.disposed === false) {
1441
1544
  /**
1442
1545
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1443
1546
  * ops getting through to the DeltaManager.
1444
1547
  * The ContainerRuntime's "connected" state simply means it is ok to send ops
1445
1548
  * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1446
1549
  */
1447
- this.context.setConnectionState(state && !readonly, this.clientId);
1550
+ this.runtime.setConnectionState(state && !readonly, this.clientId);
1551
+ }
1552
+ }
1553
+ handleDeltaConnectionArg(connectionArgs, deltaConnectionArg, canConnect = true) {
1554
+ switch (deltaConnectionArg) {
1555
+ case undefined:
1556
+ if (canConnect) {
1557
+ // connect to delta stream now since we did not before
1558
+ this.connectToDeltaStream(connectionArgs);
1559
+ }
1560
+ // intentional fallthrough
1561
+ case "delayed":
1562
+ (0, core_utils_1.assert)(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
1563
+ this.inboundQueuePausedFromInit = false;
1564
+ this._deltaManager.inbound.resume();
1565
+ this._deltaManager.inboundSignal.resume();
1566
+ break;
1567
+ case "none":
1568
+ break;
1569
+ default:
1570
+ (0, core_utils_1.unreachableCase)(deltaConnectionArg);
1448
1571
  }
1449
1572
  }
1450
1573
  }
1451
1574
  exports.Container = Container;
1452
- Container.version = "^0.1.0";
1453
1575
  //# sourceMappingURL=container.js.map