@fluidframework/container-loader 2.0.0-dev.5.3.2.178189 → 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 (173) hide show
  1. package/CHANGELOG.md +131 -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.js +2 -2
  8. package/dist/catchUpMonitor.js.map +1 -1
  9. package/dist/connectionManager.d.ts +5 -5
  10. package/dist/connectionManager.d.ts.map +1 -1
  11. package/dist/connectionManager.js +97 -93
  12. package/dist/connectionManager.js.map +1 -1
  13. package/dist/connectionStateHandler.d.ts +15 -14
  14. package/dist/connectionStateHandler.d.ts.map +1 -1
  15. package/dist/connectionStateHandler.js +50 -52
  16. package/dist/connectionStateHandler.js.map +1 -1
  17. package/dist/container.d.ts +20 -9
  18. package/dist/container.d.ts.map +1 -1
  19. package/dist/container.js +327 -277
  20. package/dist/container.js.map +1 -1
  21. package/dist/containerContext.d.ts +2 -7
  22. package/dist/containerContext.d.ts.map +1 -1
  23. package/dist/containerContext.js +2 -14
  24. package/dist/containerContext.js.map +1 -1
  25. package/dist/containerStorageAdapter.d.ts.map +1 -1
  26. package/dist/containerStorageAdapter.js +12 -13
  27. package/dist/containerStorageAdapter.js.map +1 -1
  28. package/dist/contracts.d.ts +21 -8
  29. package/dist/contracts.d.ts.map +1 -1
  30. package/dist/contracts.js +3 -3
  31. package/dist/contracts.js.map +1 -1
  32. package/dist/debugLogger.d.ts +30 -0
  33. package/dist/debugLogger.d.ts.map +1 -0
  34. package/dist/debugLogger.js +95 -0
  35. package/dist/debugLogger.js.map +1 -0
  36. package/dist/deltaManager.d.ts +21 -10
  37. package/dist/deltaManager.d.ts.map +1 -1
  38. package/dist/deltaManager.js +114 -66
  39. package/dist/deltaManager.js.map +1 -1
  40. package/dist/deltaQueue.d.ts +1 -1
  41. package/dist/deltaQueue.d.ts.map +1 -1
  42. package/dist/deltaQueue.js +10 -10
  43. package/dist/deltaQueue.js.map +1 -1
  44. package/dist/disposal.d.ts +2 -2
  45. package/dist/disposal.d.ts.map +1 -1
  46. package/dist/disposal.js +1 -1
  47. package/dist/disposal.js.map +1 -1
  48. package/dist/error.d.ts +23 -0
  49. package/dist/error.d.ts.map +1 -0
  50. package/dist/error.js +32 -0
  51. package/dist/error.js.map +1 -0
  52. package/dist/loader.d.ts +22 -3
  53. package/dist/loader.d.ts.map +1 -1
  54. package/dist/loader.js +82 -51
  55. package/dist/loader.js.map +1 -1
  56. package/dist/noopHeuristic.d.ts +2 -2
  57. package/dist/noopHeuristic.d.ts.map +1 -1
  58. package/dist/noopHeuristic.js +6 -5
  59. package/dist/noopHeuristic.js.map +1 -1
  60. package/dist/packageVersion.d.ts +1 -1
  61. package/dist/packageVersion.js +1 -1
  62. package/dist/packageVersion.js.map +1 -1
  63. package/dist/protocol.d.ts +4 -2
  64. package/dist/protocol.d.ts.map +1 -1
  65. package/dist/protocol.js +25 -4
  66. package/dist/protocol.js.map +1 -1
  67. package/dist/quorum.d.ts +4 -1
  68. package/dist/quorum.d.ts.map +1 -1
  69. package/dist/quorum.js +1 -13
  70. package/dist/quorum.js.map +1 -1
  71. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  72. package/dist/retriableDocumentStorageService.js +4 -4
  73. package/dist/retriableDocumentStorageService.js.map +1 -1
  74. package/dist/utils.d.ts +8 -1
  75. package/dist/utils.d.ts.map +1 -1
  76. package/dist/utils.js +30 -11
  77. package/dist/utils.js.map +1 -1
  78. package/lib/audience.d.ts +1 -0
  79. package/lib/audience.d.ts.map +1 -1
  80. package/lib/audience.js +4 -2
  81. package/lib/audience.js.map +1 -1
  82. package/lib/catchUpMonitor.js +1 -1
  83. package/lib/catchUpMonitor.js.map +1 -1
  84. package/lib/connectionManager.d.ts +5 -5
  85. package/lib/connectionManager.d.ts.map +1 -1
  86. package/lib/connectionManager.js +74 -67
  87. package/lib/connectionManager.js.map +1 -1
  88. package/lib/connectionStateHandler.d.ts +15 -14
  89. package/lib/connectionStateHandler.d.ts.map +1 -1
  90. package/lib/connectionStateHandler.js +27 -29
  91. package/lib/connectionStateHandler.js.map +1 -1
  92. package/lib/container.d.ts +20 -9
  93. package/lib/container.d.ts.map +1 -1
  94. package/lib/container.js +288 -238
  95. package/lib/container.js.map +1 -1
  96. package/lib/containerContext.d.ts +2 -7
  97. package/lib/containerContext.d.ts.map +1 -1
  98. package/lib/containerContext.js +2 -14
  99. package/lib/containerContext.js.map +1 -1
  100. package/lib/containerStorageAdapter.d.ts.map +1 -1
  101. package/lib/containerStorageAdapter.js +5 -6
  102. package/lib/containerStorageAdapter.js.map +1 -1
  103. package/lib/contracts.d.ts +21 -8
  104. package/lib/contracts.d.ts.map +1 -1
  105. package/lib/contracts.js +3 -3
  106. package/lib/contracts.js.map +1 -1
  107. package/lib/debugLogger.d.ts +30 -0
  108. package/lib/debugLogger.d.ts.map +1 -0
  109. package/lib/debugLogger.js +91 -0
  110. package/lib/debugLogger.js.map +1 -0
  111. package/lib/deltaManager.d.ts +21 -10
  112. package/lib/deltaManager.d.ts.map +1 -1
  113. package/lib/deltaManager.js +88 -37
  114. package/lib/deltaManager.js.map +1 -1
  115. package/lib/deltaQueue.d.ts +1 -1
  116. package/lib/deltaQueue.d.ts.map +1 -1
  117. package/lib/deltaQueue.js +3 -3
  118. package/lib/deltaQueue.js.map +1 -1
  119. package/lib/disposal.d.ts +2 -2
  120. package/lib/disposal.d.ts.map +1 -1
  121. package/lib/disposal.js +1 -1
  122. package/lib/disposal.js.map +1 -1
  123. package/lib/error.d.ts +23 -0
  124. package/lib/error.d.ts.map +1 -0
  125. package/lib/error.js +28 -0
  126. package/lib/error.js.map +1 -0
  127. package/lib/loader.d.ts +22 -3
  128. package/lib/loader.d.ts.map +1 -1
  129. package/lib/loader.js +82 -51
  130. package/lib/loader.js.map +1 -1
  131. package/lib/noopHeuristic.d.ts +2 -2
  132. package/lib/noopHeuristic.d.ts.map +1 -1
  133. package/lib/noopHeuristic.js +2 -1
  134. package/lib/noopHeuristic.js.map +1 -1
  135. package/lib/packageVersion.d.ts +1 -1
  136. package/lib/packageVersion.js +1 -1
  137. package/lib/packageVersion.js.map +1 -1
  138. package/lib/protocol.d.ts +4 -2
  139. package/lib/protocol.d.ts.map +1 -1
  140. package/lib/protocol.js +25 -4
  141. package/lib/protocol.js.map +1 -1
  142. package/lib/quorum.d.ts +4 -1
  143. package/lib/quorum.d.ts.map +1 -1
  144. package/lib/quorum.js +0 -11
  145. package/lib/quorum.js.map +1 -1
  146. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  147. package/lib/retriableDocumentStorageService.js +2 -2
  148. package/lib/retriableDocumentStorageService.js.map +1 -1
  149. package/lib/utils.d.ts +8 -1
  150. package/lib/utils.d.ts.map +1 -1
  151. package/lib/utils.js +25 -7
  152. package/lib/utils.js.map +1 -1
  153. package/package.json +26 -32
  154. package/src/audience.ts +7 -1
  155. package/src/catchUpMonitor.ts +1 -1
  156. package/src/connectionManager.ts +75 -51
  157. package/src/connectionStateHandler.ts +31 -38
  158. package/src/container.ts +335 -240
  159. package/src/containerContext.ts +0 -16
  160. package/src/containerStorageAdapter.ts +2 -1
  161. package/src/contracts.ts +27 -11
  162. package/src/debugLogger.ts +113 -0
  163. package/src/deltaManager.ts +84 -34
  164. package/src/deltaQueue.ts +2 -1
  165. package/src/disposal.ts +2 -2
  166. package/src/error.ts +44 -0
  167. package/src/loader.ts +83 -35
  168. package/src/noopHeuristic.ts +3 -2
  169. package/src/packageVersion.ts +1 -1
  170. package/src/protocol.ts +33 -2
  171. package/src/quorum.ts +0 -10
  172. package/src/retriableDocumentStorageService.ts +2 -4
  173. package/src/utils.ts +33 -8
package/lib/container.js CHANGED
@@ -5,22 +5,23 @@
5
5
  // eslint-disable-next-line import/no-internal-modules
6
6
  import merge from "lodash/merge";
7
7
  import { v4 as uuid } from "uuid";
8
- import { TypedEventEmitter, assert, performance, unreachableCase, } from "@fluidframework/common-utils";
8
+ import { assert, unreachableCase } from "@fluidframework/core-utils";
9
+ import { TypedEventEmitter, performance } from "@fluid-internal/client-utils";
10
+ import { LogLevel, } from "@fluidframework/core-interfaces";
9
11
  import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
10
- import { GenericError, UsageError } from "@fluidframework/container-utils";
11
- import { readAndParse, OnlineStatus, isOnline, combineAppAndProtocolSummary, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, canBeCoalescedByService, } from "@fluidframework/driver-utils";
12
+ import { readAndParse, OnlineStatus, isOnline, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, } from "@fluidframework/driver-utils";
12
13
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
13
- import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
14
+ import { createChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, connectedEventName, normalizeError, createChildMonitoringContext, wrapError, formatTick, GenericError, UsageError, } from "@fluidframework/telemetry-utils";
14
15
  import { Audience } from "./audience";
15
16
  import { ContainerContext } from "./containerContext";
16
- import { ReconnectMode, getPackageName } from "./contracts";
17
+ import { ReconnectMode, getPackageName, } from "./contracts";
17
18
  import { DeltaManager } from "./deltaManager";
18
19
  import { RelativeLoader } from "./loader";
19
20
  import { pkgVersion } from "./packageVersion";
20
21
  import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTreeWithBlobContents, } from "./containerStorageAdapter";
21
22
  import { createConnectionStateHandler } from "./connectionStateHandler";
22
- import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
23
- import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
23
+ import { combineAppAndProtocolSummary, getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer, } from "./utils";
24
+ import { initQuorumValuesFromCodeDetails } from "./quorum";
24
25
  import { NoopHeuristic } from "./noopHeuristic";
25
26
  import { ConnectionManager } from "./connectionManager";
26
27
  import { ConnectionState } from "./connectionState";
@@ -104,7 +105,7 @@ export async function waitContainerToCatchUp(container) {
104
105
  }
105
106
  const getCodeProposal =
106
107
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
107
- (quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
108
+ (quorum) => quorum.get("code") ?? quorum.get("code2");
108
109
  /**
109
110
  * Helper function to report to telemetry cases where operation takes longer than expected (200ms)
110
111
  * @param logger - logger to use
@@ -124,7 +125,6 @@ export class Container extends EventEmitterWithErrorHandling {
124
125
  * @internal
125
126
  */
126
127
  constructor(createProps, loadProps) {
127
- var _a;
128
128
  super((name, error) => {
129
129
  this.mc.logger.sendErrorEvent({
130
130
  eventName: "ContainerEventHandlerException",
@@ -152,7 +152,6 @@ export class Container extends EventEmitterWithErrorHandling {
152
152
  this.inboundQueuePausedFromInit = true;
153
153
  this.firstConnection = true;
154
154
  this.connectionTransitionTimes = [];
155
- this.messageCountAfterDisconnection = 0;
156
155
  this.attachStarted = false;
157
156
  this._dirtyContainer = false;
158
157
  this.savedOps = [];
@@ -175,8 +174,9 @@ export class Container extends EventEmitterWithErrorHandling {
175
174
  };
176
175
  const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
177
176
  this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
178
- const pendingLocalState = loadProps === null || loadProps === void 0 ? void 0 : loadProps.pendingLocalState;
179
- this._canReconnect = canReconnect !== null && canReconnect !== void 0 ? canReconnect : true;
177
+ const pendingLocalState = loadProps?.pendingLocalState;
178
+ this._clientId = pendingLocalState?.clientId;
179
+ this._canReconnect = canReconnect ?? true;
180
180
  this.clientDetailsOverride = clientDetailsOverride;
181
181
  this.urlResolver = urlResolver;
182
182
  this.serviceFactory = documentServiceFactory;
@@ -184,14 +184,18 @@ export class Container extends EventEmitterWithErrorHandling {
184
184
  // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
185
185
  // all clients that were loaded from the same loader (including summarizer clients).
186
186
  // Tracking alternative ways to handle this in AB#4129.
187
- this.options = Object.assign({}, options);
187
+ this.options = { ...options };
188
188
  this.scope = scope;
189
189
  this.detachedBlobStorage = detachedBlobStorage;
190
190
  this.protocolHandlerBuilder =
191
- protocolHandlerBuilder !== null && protocolHandlerBuilder !== void 0 ? protocolHandlerBuilder : ((...args) => new ProtocolHandler(...args, new Audience()));
191
+ protocolHandlerBuilder ??
192
+ ((attributes, quorumSnapshot, sendProposal) => new ProtocolHandler(attributes, quorumSnapshot, sendProposal, new Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
192
193
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
193
194
  this.clone = async (_loadProps, createParamOverrides) => {
194
- return Container.load(_loadProps, Object.assign(Object.assign({}, createProps), createParamOverrides));
195
+ return Container.load(_loadProps, {
196
+ ...createProps,
197
+ ...createParamOverrides,
198
+ });
195
199
  };
196
200
  // Create logger for data stores to use
197
201
  const type = this.client.details.type;
@@ -199,50 +203,50 @@ export class Container extends EventEmitterWithErrorHandling {
199
203
  const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
200
204
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
201
205
  // We assign the id later so property getter is used.
202
- this.subLogger = ChildLogger.create(subLogger, undefined, {
203
- all: {
204
- clientType,
205
- containerId: uuid(),
206
- docId: () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; },
207
- containerAttachState: () => this._attachState,
208
- containerLifecycleState: () => this._lifecycleState,
209
- containerConnectionState: () => ConnectionState[this.connectionState],
210
- serializedContainer: pendingLocalState !== undefined,
211
- },
212
- // we need to be judicious with our logging here to avoid generating too much data
213
- // all data logged here should be broadly applicable, and not specific to a
214
- // specific error or class of errors
215
- error: {
216
- // load information to associate errors with the specific load point
217
- dmInitialSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.initialSequenceNumber; },
218
- dmLastProcessedSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastSequenceNumber; },
219
- dmLastKnownSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastKnownSeqNumber; },
220
- containerLoadedFromVersionId: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
221
- containerLoadedFromVersionDate: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
222
- // message information to associate errors with the specific execution state
223
- // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
224
- 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; },
225
- 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; },
226
- dmLastMsqSeqClientId: () => {
227
- var _a, _b, _c, _d;
228
- return ((_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId) === null
206
+ this.subLogger = createChildLogger({
207
+ logger: subLogger,
208
+ properties: {
209
+ all: {
210
+ clientType,
211
+ containerId: uuid(),
212
+ docId: () => this.resolvedUrl?.id,
213
+ containerAttachState: () => this._attachState,
214
+ containerLifecycleState: () => this._lifecycleState,
215
+ containerConnectionState: () => ConnectionState[this.connectionState],
216
+ serializedContainer: pendingLocalState !== undefined,
217
+ },
218
+ // we need to be judicious with our logging here to avoid generating too much data
219
+ // all data logged here should be broadly applicable, and not specific to a
220
+ // specific error or class of errors
221
+ error: {
222
+ // load information to associate errors with the specific load point
223
+ dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
224
+ dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
225
+ dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
226
+ containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
227
+ containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
228
+ // message information to associate errors with the specific execution state
229
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
230
+ dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
231
+ dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
232
+ dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
229
233
  ? "null"
230
- : (_d = (_c = this.deltaManager) === null || _c === void 0 ? void 0 : _c.lastMessage) === null || _d === void 0 ? void 0 : _d.clientId;
234
+ : this.deltaManager?.lastMessage?.clientId,
235
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
236
+ connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
231
237
  },
232
- 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; },
233
- connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
234
238
  },
235
239
  });
236
240
  // Prefix all events in this file with container-loader
237
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
241
+ this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
238
242
  this._deltaManager = this.createDeltaManager();
239
243
  this.connectionStateHandler = createConnectionStateHandler({
240
244
  logger: this.mc.logger,
241
- connectionStateChanged: (value, oldState, reason, error) => {
245
+ connectionStateChanged: (value, oldState, reason) => {
242
246
  if (value === ConnectionState.Connected) {
243
247
  this._clientId = this.connectionStateHandler.pendingClientId;
244
248
  }
245
- this.logConnectionStateChangeTelemetry(value, oldState, reason, error);
249
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
246
250
  if (this._lifecycleState === "loaded") {
247
251
  this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
248
252
  ? reason
@@ -258,9 +262,14 @@ export class Container extends EventEmitterWithErrorHandling {
258
262
  // Report issues only if we already loaded container - op processing is paused while container is loading,
259
263
  // so we always time-out processing of join op in cases where fetching snapshot takes a minute.
260
264
  // It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
261
- this._deltaManager.logConnectionIssue(Object.assign({ eventName,
262
- mode, category: this._lifecycleState === "loading" ? "generic" : category, duration: performance.now() -
263
- this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
265
+ this._deltaManager.logConnectionIssue({
266
+ eventName,
267
+ mode,
268
+ category: this._lifecycleState === "loading" ? "generic" : category,
269
+ duration: performance.now() -
270
+ this.connectionTransitionTimes[ConnectionState.CatchingUp],
271
+ ...(details === undefined ? {} : { details: JSON.stringify(details) }),
272
+ });
264
273
  // If this is "write" connection, it took too long to receive join op. But in most cases that's due
265
274
  // to very slow op fetches and we will eventually get there.
266
275
  // For "read" connections, we get here due to self join signal not arriving on time. We will need to
@@ -270,14 +279,15 @@ export class Container extends EventEmitterWithErrorHandling {
270
279
  // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
271
280
  // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
272
281
  if (mode === "read") {
273
- this.disconnect();
274
- this.connect();
282
+ const reason = { text: "NoJoinSignal" };
283
+ this.disconnectInternal(reason);
284
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
275
285
  }
276
286
  },
277
287
  clientShouldHaveLeft: (clientId) => {
278
288
  this.clientsWhoShouldHaveLeft.add(clientId);
279
289
  },
280
- }, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
290
+ }, this.deltaManager, pendingLocalState?.clientId);
281
291
  this.on(savedContainerEvent, () => {
282
292
  this.connectionStateHandler.containerSaved();
283
293
  });
@@ -289,14 +299,15 @@ export class Container extends EventEmitterWithErrorHandling {
289
299
  : combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
290
300
  // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
291
301
  // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
292
- const forceEnableSummarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _a !== void 0 ? _a : options.summarizeProtocolTree;
293
- this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
302
+ const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
303
+ options.summarizeProtocolTree;
304
+ this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
294
305
  const isDomAvailable = typeof document === "object" &&
295
306
  document !== null &&
296
307
  typeof document.addEventListener === "function" &&
297
308
  document.addEventListener !== null;
298
- // keep track of last time page was visible for telemetry
299
- if (isDomAvailable) {
309
+ // keep track of last time page was visible for telemetry (on interactive clients only)
310
+ if (isDomAvailable && interactive) {
300
311
  this.lastVisible = document.hidden ? performance.now() : undefined;
301
312
  this.visibilityEventHandler = () => {
302
313
  if (document.hidden) {
@@ -317,7 +328,7 @@ export class Container extends EventEmitterWithErrorHandling {
317
328
  * @internal
318
329
  */
319
330
  static async load(loadProps, createProps) {
320
- const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
331
+ const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } = loadProps;
321
332
  const container = new Container(createProps, loadProps);
322
333
  const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
323
334
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
@@ -325,26 +336,31 @@ export class Container extends EventEmitterWithErrorHandling {
325
336
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
326
337
  // to return container, so ignore this value and use undefined for opsBeforeReturn
327
338
  const mode = pendingLocalState
328
- ? Object.assign(Object.assign({}, (loadMode !== null && loadMode !== void 0 ? loadMode : defaultMode)), { opsBeforeReturn: undefined }) : loadMode !== null && loadMode !== void 0 ? loadMode : defaultMode;
339
+ ? { ...(loadMode ?? defaultMode), opsBeforeReturn: undefined }
340
+ : loadMode ?? defaultMode;
329
341
  const onClosed = (err) => {
330
342
  // pre-0.58 error message: containerClosedWithoutErrorDuringLoad
331
- reject(err !== null && err !== void 0 ? err : new GenericError("Container closed without error during load"));
343
+ reject(err ?? new GenericError("Container closed without error during load"));
332
344
  };
333
345
  container.on("closed", onClosed);
334
346
  container
335
- .load(version, mode, resolvedUrl, pendingLocalState)
347
+ .load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
336
348
  .finally(() => {
337
349
  container.removeListener("closed", onClosed);
338
350
  })
339
351
  .then((props) => {
340
- event.end(Object.assign(Object.assign({}, props), loadMode));
352
+ event.end({ ...props, ...loadMode });
341
353
  resolve(container);
342
354
  }, (error) => {
343
355
  const err = normalizeError(error);
344
356
  // Depending where error happens, we can be attempting to connect to web socket
345
357
  // and continuously retrying (consider offline mode)
346
358
  // Host has no container to close, so it's prudent to do it here
359
+ // Note: We could only dispose the container instead of just close but that would
360
+ // the telemetry where users sometimes search for ContainerClose event to look
361
+ // for load failures. So not removing this at this time.
347
362
  container.close(err);
363
+ container.dispose(err);
348
364
  onClosed(err);
349
365
  });
350
366
  }), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
@@ -381,10 +397,10 @@ export class Container extends EventEmitterWithErrorHandling {
381
397
  }
382
398
  }
383
399
  get closed() {
384
- return (this._lifecycleState === "closing" ||
385
- this._lifecycleState === "closed" ||
386
- this._lifecycleState === "disposing" ||
387
- this._lifecycleState === "disposed");
400
+ return (this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed);
401
+ }
402
+ get disposed() {
403
+ return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
388
404
  }
389
405
  get runtime() {
390
406
  if (this._runtime === undefined) {
@@ -405,7 +421,6 @@ export class Container extends EventEmitterWithErrorHandling {
405
421
  return this;
406
422
  }
407
423
  get resolvedUrl() {
408
- var _a;
409
424
  /**
410
425
  * All attached containers will have a document service,
411
426
  * this is required, as attached containers are attached to
@@ -417,7 +432,7 @@ export class Container extends EventEmitterWithErrorHandling {
417
432
  * is always the same as the containers, as we had to
418
433
  * obtain the resolved url, and then create the service from it.
419
434
  */
420
- return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
435
+ return this.service?.resolvedUrl;
421
436
  }
422
437
  get readOnlyInfo() {
423
438
  return this._deltaManager.readOnlyInfo;
@@ -445,8 +460,8 @@ export class Container extends EventEmitterWithErrorHandling {
445
460
  return this._clientId;
446
461
  }
447
462
  get offlineLoadEnabled() {
448
- var _a, _b;
449
- 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;
463
+ const enabled = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
464
+ this.options?.enableOfflineLoad === true;
450
465
  // summarizer will not have any pending state we want to save
451
466
  return enabled && this.deltaManager.clientDetails.capabilities.interactive;
452
467
  }
@@ -483,18 +498,16 @@ export class Container extends EventEmitterWithErrorHandling {
483
498
  * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
484
499
  */
485
500
  async getEntryPoint() {
486
- var _a, _b;
487
501
  if (this._disposed) {
488
502
  throw new UsageError("The context is already disposed");
489
503
  }
490
504
  if (this._runtime !== undefined) {
491
- return (_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a);
505
+ return this._runtime.getEntryPoint?.();
492
506
  }
493
507
  return new Promise((resolve, reject) => {
494
508
  const runtimeInstantiatedHandler = () => {
495
- var _a, _b;
496
509
  assert(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
497
- resolve((_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
510
+ resolve(this._runtime.getEntryPoint?.());
498
511
  this._lifecycleEvents.off("disposed", disposedHandler);
499
512
  };
500
513
  const disposedHandler = () => {
@@ -528,7 +541,6 @@ export class Container extends EventEmitterWithErrorHandling {
528
541
  assert(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
529
542
  }
530
543
  closeCore(error) {
531
- var _a;
532
544
  assert(!this.closed, 0x315 /* re-entrancy */);
533
545
  try {
534
546
  // Ensure that we raise all key events even if one of these throws
@@ -544,7 +556,7 @@ export class Container extends EventEmitterWithErrorHandling {
544
556
  : "generic",
545
557
  }, error);
546
558
  this._lifecycleState = "closing";
547
- (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
559
+ this._protocolHandler?.close();
548
560
  this.connectionStateHandler.dispose();
549
561
  }
550
562
  catch (exception) {
@@ -564,7 +576,6 @@ export class Container extends EventEmitterWithErrorHandling {
564
576
  }
565
577
  }
566
578
  disposeCore(error) {
567
- var _a, _b, _c;
568
579
  assert(!this._disposed, 0x54c /* Container already disposed */);
569
580
  this._disposed = true;
570
581
  try {
@@ -581,15 +592,15 @@ export class Container extends EventEmitterWithErrorHandling {
581
592
  if (this._lifecycleState !== "closed") {
582
593
  this._lifecycleState = "disposing";
583
594
  }
584
- (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
595
+ this._protocolHandler?.close();
585
596
  this.connectionStateHandler.dispose();
586
597
  const maybeError = error !== undefined ? new Error(error.message) : undefined;
587
- (_b = this._runtime) === null || _b === void 0 ? void 0 : _b.dispose(maybeError);
598
+ this._runtime?.dispose(maybeError);
588
599
  this.storageAdapter.dispose();
589
600
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
590
601
  // about file, like file being overwritten in storage, but client having stale local cache.
591
602
  // Driver need to ensure all caches are cleared on critical errors
592
- (_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
603
+ this.service?.dispose(error);
593
604
  }
594
605
  catch (exception) {
595
606
  this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
@@ -605,36 +616,48 @@ export class Container extends EventEmitterWithErrorHandling {
605
616
  this._lifecycleEvents.emit("disposed");
606
617
  }
607
618
  }
608
- closeAndGetPendingLocalState() {
619
+ async closeAndGetPendingLocalState() {
609
620
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
610
621
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
611
622
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
612
- const pendingState = this.getPendingLocalState();
623
+ this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
624
+ const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
613
625
  this.close();
614
626
  return pendingState;
615
627
  }
616
- getPendingLocalState() {
617
- if (!this.offlineLoadEnabled) {
618
- throw new UsageError("Can't get pending local state unless offline load is enabled");
619
- }
620
- if (this.closed || this._disposed) {
621
- throw new UsageError("Pending state cannot be retried if the container is closed or disposed");
622
- }
623
- assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
624
- assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
625
- assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
626
- assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
627
- const pendingState = {
628
- pendingRuntimeState: this.runtime.getPendingLocalState(),
629
- baseSnapshot: this.baseSnapshot,
630
- snapshotBlobs: this.baseSnapshotBlobs,
631
- savedOps: this.savedOps,
632
- url: this.resolvedUrl.url,
633
- term: OnlyValidTermValue,
628
+ async getPendingLocalState() {
629
+ return this.getPendingLocalStateCore({ notifyImminentClosure: false });
630
+ }
631
+ async getPendingLocalStateCore(props) {
632
+ return PerformanceEvent.timedExecAsync(this.mc.logger, {
633
+ eventName: "getPendingLocalState",
634
+ notifyImminentClosure: props.notifyImminentClosure,
635
+ savedOpsSize: this.savedOps.length,
634
636
  clientId: this.clientId,
635
- };
636
- this.mc.logger.sendTelemetryEvent({ eventName: "GetPendingLocalState" });
637
- return JSON.stringify(pendingState);
637
+ }, async () => {
638
+ if (!this.offlineLoadEnabled) {
639
+ throw new UsageError("Can't get pending local state unless offline load is enabled");
640
+ }
641
+ if (this.closed || this._disposed) {
642
+ throw new UsageError("Pending state cannot be retried if the container is closed or disposed");
643
+ }
644
+ assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
645
+ assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
646
+ assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
647
+ assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
648
+ const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
649
+ const pendingState = {
650
+ pendingRuntimeState,
651
+ baseSnapshot: this.baseSnapshot,
652
+ snapshotBlobs: this.baseSnapshotBlobs,
653
+ savedOps: this.savedOps,
654
+ url: this.resolvedUrl.url,
655
+ term: OnlyValidTermValue,
656
+ // no need to save this if there is no pending runtime state
657
+ clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
658
+ };
659
+ return JSON.stringify(pendingState);
660
+ });
638
661
  }
639
662
  get attachState() {
640
663
  return this._attachState;
@@ -652,9 +675,8 @@ export class Container extends EventEmitterWithErrorHandling {
652
675
  }
653
676
  return JSON.stringify(combinedSummary);
654
677
  }
655
- async attach(request) {
678
+ async attach(request, attachProps) {
656
679
  await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
657
- var _a;
658
680
  if (this._lifecycleState !== "loaded") {
659
681
  // pre-0.58 error message: containerNotValidForAttach
660
682
  throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
@@ -737,16 +759,16 @@ export class Container extends EventEmitterWithErrorHandling {
737
759
  this.runtime.setAttachState(AttachState.Attached);
738
760
  this.emit("attached");
739
761
  if (!this.closed) {
740
- this.resumeInternal({
762
+ this.handleDeltaConnectionArg({
741
763
  fetchOpsFromStorage: false,
742
- reason: "createDetached",
743
- });
764
+ reason: { text: "createDetached" },
765
+ }, attachProps?.deltaConnection);
744
766
  }
745
767
  }
746
768
  catch (error) {
747
769
  // add resolved URL on error object so that host has the ability to find this document and delete it
748
770
  const newError = normalizeError(error);
749
- newError.addTelemetryProperties({ resolvedUrl: (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.url });
771
+ newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
750
772
  this.close(newError);
751
773
  throw newError;
752
774
  }
@@ -755,7 +777,7 @@ export class Container extends EventEmitterWithErrorHandling {
755
777
  async request(path) {
756
778
  return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
757
779
  }
758
- setAutoReconnectInternal(mode) {
780
+ setAutoReconnectInternal(mode, reason) {
759
781
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
760
782
  if (currentMode === mode) {
761
783
  return;
@@ -769,7 +791,7 @@ export class Container extends EventEmitterWithErrorHandling {
769
791
  connectionState: ConnectionState[this.connectionState],
770
792
  duration,
771
793
  });
772
- this._deltaManager.connectionManager.setAutoReconnect(mode);
794
+ this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
773
795
  }
774
796
  connect() {
775
797
  if (this.closed) {
@@ -782,7 +804,10 @@ export class Container extends EventEmitterWithErrorHandling {
782
804
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
783
805
  // If there is gap, we will learn about it once connected, but the gap should be small (if any),
784
806
  // assuming that connect() is called quickly after initial container boot.
785
- this.connectInternal({ reason: "DocumentConnect", fetchOpsFromStorage: false });
807
+ this.connectInternal({
808
+ reason: { text: "DocumentConnect" },
809
+ fetchOpsFromStorage: false,
810
+ });
786
811
  }
787
812
  }
788
813
  connectInternal(args) {
@@ -792,21 +817,21 @@ export class Container extends EventEmitterWithErrorHandling {
792
817
  this.resumeInternal(args);
793
818
  // Set Auto Reconnect Mode
794
819
  const mode = ReconnectMode.Enabled;
795
- this.setAutoReconnectInternal(mode);
820
+ this.setAutoReconnectInternal(mode, args.reason);
796
821
  }
797
822
  disconnect() {
798
823
  if (this.closed) {
799
824
  throw new UsageError(`The Container is closed and cannot be disconnected`);
800
825
  }
801
826
  else {
802
- this.disconnectInternal();
827
+ this.disconnectInternal({ text: "DocumentDisconnect" });
803
828
  }
804
829
  }
805
- disconnectInternal() {
830
+ disconnectInternal(reason) {
806
831
  assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
807
832
  // Set Auto Reconnect Mode
808
833
  const mode = ReconnectMode.Disabled;
809
- this.setAutoReconnectInternal(mode);
834
+ this.setAutoReconnectInternal(mode, reason);
810
835
  }
811
836
  resumeInternal(args) {
812
837
  assert(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
@@ -853,7 +878,6 @@ export class Container extends EventEmitterWithErrorHandling {
853
878
  * Determines if the currently loaded module satisfies the incoming constraint code details
854
879
  */
855
880
  async satisfies(constraintCodeDetails) {
856
- var _a, _b;
857
881
  // If we have no module, it can't satisfy anything.
858
882
  if (this._loadedModule === undefined) {
859
883
  return false;
@@ -863,8 +887,8 @@ export class Container extends EventEmitterWithErrorHandling {
863
887
  if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
864
888
  comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
865
889
  }
866
- const maybeCompareExport = (_a = this._loadedModule) === null || _a === void 0 ? void 0 : _a.module.fluidExport;
867
- if ((maybeCompareExport === null || maybeCompareExport === void 0 ? void 0 : maybeCompareExport.IFluidCodeDetailsComparer) !== undefined) {
890
+ const maybeCompareExport = this._loadedModule?.module.fluidExport;
891
+ if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
868
892
  comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
869
893
  }
870
894
  // If there are no comparers, then it's impossible to know if the currently loaded package satisfies
@@ -874,7 +898,7 @@ export class Container extends EventEmitterWithErrorHandling {
874
898
  return false;
875
899
  }
876
900
  for (const comparer of comparers) {
877
- const satisfies = await comparer.satisfies((_b = this._loadedModule) === null || _b === void 0 ? void 0 : _b.details, constraintCodeDetails);
901
+ const satisfies = await comparer.satisfies(this._loadedModule?.details, constraintCodeDetails);
878
902
  if (satisfies === false) {
879
903
  return false;
880
904
  }
@@ -897,8 +921,8 @@ export class Container extends EventEmitterWithErrorHandling {
897
921
  *
898
922
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
899
923
  */
900
- async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
901
- var _a, _b, _c;
924
+ async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
925
+ const timings = { phase1: performance.now() };
902
926
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
903
927
  // Ideally we always connect as "read" by default.
904
928
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
@@ -910,7 +934,7 @@ export class Container extends EventEmitterWithErrorHandling {
910
934
  // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
911
935
  // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
912
936
  const connectionArgs = {
913
- reason: "DocumentOpen",
937
+ reason: { text: "DocumentOpen" },
914
938
  mode: "write",
915
939
  fetchOpsFromStorage: false,
916
940
  };
@@ -929,6 +953,7 @@ export class Container extends EventEmitterWithErrorHandling {
929
953
  });
930
954
  }
931
955
  this._attachState = AttachState.Attached;
956
+ timings.phase2 = performance.now();
932
957
  // Fetch specified snapshot.
933
958
  const { snapshot, versionId } = pendingLocalState === undefined
934
959
  ? await this.fetchSnapshotTree(specifiedVersion)
@@ -947,9 +972,51 @@ export class Container extends EventEmitterWithErrorHandling {
947
972
  }
948
973
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
949
974
  // If we saved ops, we will replay them and don't need DeltaManager to fetch them
950
- const sequenceNumber = (_a = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.savedOps[pendingLocalState.savedOps.length - 1]) === null || _a === void 0 ? void 0 : _a.sequenceNumber;
951
- const dmAttributes = sequenceNumber !== undefined ? Object.assign(Object.assign({}, attributes), { sequenceNumber }) : attributes;
975
+ const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
976
+ const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
952
977
  let opsBeforeReturnP;
978
+ if (loadMode.pauseAfterLoad === true) {
979
+ // If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
980
+ if (loadMode.opsBeforeReturn === "sequenceNumber") {
981
+ assert(loadToSequenceNumber !== undefined, 0x727 /* sequenceNumber should be defined */);
982
+ // Note: It is possible that we think the latest snapshot is newer than the specified sequence number
983
+ // due to saved ops that may be replayed after the snapshot.
984
+ // https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
985
+ if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
986
+ 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.");
987
+ }
988
+ }
989
+ // Force readonly mode - this will ensure we don't receive an error for the lack of join op
990
+ this.forceReadonly(true);
991
+ // We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
992
+ const opHandler = () => {
993
+ if (loadToSequenceNumber === undefined) {
994
+ // If there is no specified sequence number, pause after the inbound queue is empty.
995
+ if (this.deltaManager.inbound.length !== 0) {
996
+ return;
997
+ }
998
+ }
999
+ else {
1000
+ // If there is a specified sequence number, keep processing until we reach it.
1001
+ if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
1002
+ return;
1003
+ }
1004
+ }
1005
+ // Pause op processing once we have processed the desired number of ops.
1006
+ void this.deltaManager.inbound.pause();
1007
+ void this.deltaManager.outbound.pause();
1008
+ this.off("op", opHandler);
1009
+ };
1010
+ if ((loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
1011
+ this.deltaManager.lastSequenceNumber === loadToSequenceNumber) {
1012
+ // If we have already reached the desired sequence number, call opHandler() to pause immediately.
1013
+ opHandler();
1014
+ }
1015
+ else {
1016
+ // If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
1017
+ this.on("op", opHandler);
1018
+ }
1019
+ }
953
1020
  // Attach op handlers to finish initialization and be able to start processing ops
954
1021
  // Kick off any ops fetching if required.
955
1022
  switch (loadMode.opsBeforeReturn) {
@@ -958,11 +1025,10 @@ export class Container extends EventEmitterWithErrorHandling {
958
1025
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
959
1026
  this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
960
1027
  break;
1028
+ case "sequenceNumber":
961
1029
  case "cached":
962
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
963
- break;
964
1030
  case "all":
965
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
1031
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, loadMode.opsBeforeReturn);
966
1032
  break;
967
1033
  default:
968
1034
  unreachableCase(loadMode.opsBeforeReturn);
@@ -970,20 +1036,19 @@ export class Container extends EventEmitterWithErrorHandling {
970
1036
  // ...load in the existing quorum
971
1037
  // Initialize the protocol handler
972
1038
  await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1039
+ timings.phase3 = performance.now();
973
1040
  const codeDetails = this.getCodeDetailsFromQuorum();
974
- await this.instantiateContext(true, // existing
975
- codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
1041
+ await this.instantiateRuntime(codeDetails, snapshot,
1042
+ // give runtime a dummy value so it knows we're loading from a stash blob
1043
+ pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined);
976
1044
  // replay saved ops
977
1045
  if (pendingLocalState) {
978
1046
  for (const message of pendingLocalState.savedOps) {
979
1047
  this.processRemoteMessage(message);
980
1048
  // allow runtime to apply stashed ops at this op's sequence number
981
- await ((_c = (_b = this.runtime).notifyOpReplay) === null || _c === void 0 ? void 0 : _c.call(_b, message));
1049
+ await this.runtime.notifyOpReplay?.(message);
982
1050
  }
983
1051
  pendingLocalState.savedOps = [];
984
- // now set clientId to stashed clientId so live ops are correctly processed as local
985
- assert(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
986
- this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
987
1052
  }
988
1053
  // We might have hit some failure that did not manifest itself in exception in this flow,
989
1054
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
@@ -995,24 +1060,20 @@ export class Container extends EventEmitterWithErrorHandling {
995
1060
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
996
1061
  this._deltaManager.inbound.pause();
997
1062
  }
998
- switch (loadMode.deltaConnection) {
999
- case undefined:
1000
- if (pendingLocalState) {
1001
- // connect to delta stream now since we did not before
1002
- this.connectToDeltaStream(connectionArgs);
1063
+ this.handleDeltaConnectionArg(connectionArgs, loadMode.deltaConnection, pendingLocalState !== undefined);
1064
+ }
1065
+ // If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
1066
+ if (loadToSequenceNumber !== undefined &&
1067
+ this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
1068
+ await new Promise((resolve, reject) => {
1069
+ const opHandler = (message) => {
1070
+ if (message.sequenceNumber >= loadToSequenceNumber) {
1071
+ resolve();
1072
+ this.off("op", opHandler);
1003
1073
  }
1004
- // intentional fallthrough
1005
- case "delayed":
1006
- assert(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
1007
- this.inboundQueuePausedFromInit = false;
1008
- this._deltaManager.inbound.resume();
1009
- this._deltaManager.inboundSignal.resume();
1010
- break;
1011
- case "none":
1012
- break;
1013
- default:
1014
- unreachableCase(loadMode.deltaConnection);
1015
- }
1074
+ };
1075
+ this.on("op", opHandler);
1076
+ });
1016
1077
  }
1017
1078
  // Safety net: static version of Container.load() should have learned about it through "closed" handler.
1018
1079
  // But if that did not happen for some reason, fail load for sure.
@@ -1024,6 +1085,11 @@ export class Container extends EventEmitterWithErrorHandling {
1024
1085
  }
1025
1086
  // Internal context is fully loaded at this point
1026
1087
  this.setLoaded();
1088
+ timings.end = performance.now();
1089
+ this.subLogger.sendTelemetryEvent({
1090
+ eventName: "LoadStagesTimings",
1091
+ details: JSON.stringify(timings),
1092
+ }, undefined, LogLevel.verbose);
1027
1093
  return {
1028
1094
  sequenceNumber: attributes.sequenceNumber,
1029
1095
  version: versionId,
@@ -1031,7 +1097,7 @@ export class Container extends EventEmitterWithErrorHandling {
1031
1097
  dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
1032
1098
  };
1033
1099
  }
1034
- async createDetached(source) {
1100
+ async createDetached(codeDetails) {
1035
1101
  const attributes = {
1036
1102
  sequenceNumber: detachedContainerRefSeqNumber,
1037
1103
  term: OnlyValidTermValue,
@@ -1039,14 +1105,13 @@ export class Container extends EventEmitterWithErrorHandling {
1039
1105
  };
1040
1106
  await this.attachDeltaManagerOpHandler(attributes);
1041
1107
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1042
- const qValues = initQuorumValuesFromCodeDetails(source);
1108
+ const qValues = initQuorumValuesFromCodeDetails(codeDetails);
1043
1109
  this.initializeProtocolState(attributes, {
1044
1110
  members: [],
1045
1111
  proposals: [],
1046
1112
  values: qValues,
1047
1113
  });
1048
- // The load context - given we seeded the quorum - will be great
1049
- await this.instantiateContextDetached(false);
1114
+ await this.instantiateRuntime(codeDetails, undefined);
1050
1115
  this.setLoaded();
1051
1116
  }
1052
1117
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
@@ -1061,14 +1126,13 @@ export class Container extends EventEmitterWithErrorHandling {
1061
1126
  // Initialize the protocol handler
1062
1127
  const baseTree = getProtocolSnapshotTree(snapshotTree);
1063
1128
  const qValues = await readAndParse(this.storageAdapter, baseTree.blobs.quorumValues);
1064
- const codeDetails = getCodeDetailsFromQuorumValues(qValues);
1065
1129
  this.initializeProtocolState(attributes, {
1066
1130
  members: [],
1067
1131
  proposals: [],
1068
- values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
1132
+ values: qValues,
1069
1133
  });
1070
- await this.instantiateContextDetached(true, // existing
1071
- snapshotTree);
1134
+ const codeDetails = this.getCodeDetailsFromQuorum();
1135
+ await this.instantiateRuntime(codeDetails, snapshotTree);
1072
1136
  this.setLoaded();
1073
1137
  }
1074
1138
  async getDocumentAttributes(storage, tree) {
@@ -1105,7 +1169,10 @@ export class Container extends EventEmitterWithErrorHandling {
1105
1169
  }
1106
1170
  initializeProtocolState(attributes, quorumSnapshot) {
1107
1171
  const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })));
1108
- const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
1172
+ const protocolLogger = createChildLogger({
1173
+ logger: this.subLogger,
1174
+ namespace: "ProtocolHandler",
1175
+ });
1109
1176
  protocol.quorum.on("error", (error) => {
1110
1177
  protocolLogger.sendErrorEvent(error);
1111
1178
  });
@@ -1167,8 +1234,7 @@ export class Container extends EventEmitterWithErrorHandling {
1167
1234
  return pkg;
1168
1235
  }
1169
1236
  get client() {
1170
- var _a;
1171
- const client = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.client) !== undefined
1237
+ const client = this.options?.client !== undefined
1172
1238
  ? this.options.client
1173
1239
  : {
1174
1240
  details: {
@@ -1199,7 +1265,7 @@ export class Container extends EventEmitterWithErrorHandling {
1199
1265
  }
1200
1266
  createDeltaManager() {
1201
1267
  const serviceProvider = () => this.service;
1202
- const deltaManager = new DeltaManager(serviceProvider, ChildLogger.create(this.subLogger, "DeltaManager"), () => this.activeConnection(), (props) => new ConnectionManager(serviceProvider, () => this.isDirty, this.client, this._canReconnect, ChildLogger.create(this.subLogger, "ConnectionManager"), props));
1268
+ const deltaManager = new DeltaManager(serviceProvider, createChildLogger({ logger: this.subLogger, namespace: "DeltaManager" }), () => this.activeConnection(), (props) => new ConnectionManager(serviceProvider, () => this.isDirty, this.client, this._canReconnect, createChildLogger({ logger: this.subLogger, namespace: "ConnectionManager" }), props));
1203
1269
  // Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
1204
1270
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1205
1271
  deltaManager.inbound.pause();
@@ -1215,11 +1281,10 @@ export class Container extends EventEmitterWithErrorHandling {
1215
1281
  deltaManager.on("cancelEstablishingConnection", (reason) => {
1216
1282
  this.connectionStateHandler.cancelEstablishingConnection(reason);
1217
1283
  });
1218
- deltaManager.on("disconnect", (reason, error) => {
1219
- var _a;
1220
- (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
1284
+ deltaManager.on("disconnect", (reason) => {
1285
+ this.noopHeuristic?.notifyDisconnect();
1221
1286
  if (!this.closed) {
1222
- this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1287
+ this.connectionStateHandler.receivedDisconnectEvent(reason);
1223
1288
  }
1224
1289
  });
1225
1290
  deltaManager.on("throttled", (warning) => {
@@ -1251,8 +1316,7 @@ export class Container extends EventEmitterWithErrorHandling {
1251
1316
  },
1252
1317
  }, prefetchType);
1253
1318
  }
1254
- logConnectionStateChangeTelemetry(value, oldState, reason, error) {
1255
- var _a;
1319
+ logConnectionStateChangeTelemetry(value, oldState, reason) {
1256
1320
  // Log actual event
1257
1321
  const time = performance.now();
1258
1322
  this.connectionTransitionTimes[value] = time;
@@ -1269,7 +1333,7 @@ export class Container extends EventEmitterWithErrorHandling {
1269
1333
  if (value === ConnectionState.Connected) {
1270
1334
  durationFromDisconnected =
1271
1335
  time - this.connectionTransitionTimes[ConnectionState.Disconnected];
1272
- durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
1336
+ durationFromDisconnected = formatTick(durationFromDisconnected);
1273
1337
  }
1274
1338
  else if (value === ConnectionState.CatchingUp) {
1275
1339
  // This info is of most interesting while Catching Up.
@@ -1282,19 +1346,31 @@ export class Container extends EventEmitterWithErrorHandling {
1282
1346
  }
1283
1347
  connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1284
1348
  }
1285
- this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${ConnectionState[value]}`, from: ConnectionState[oldState], duration,
1349
+ this.mc.logger.sendPerformanceEvent({
1350
+ eventName: `ConnectionStateChange_${ConnectionState[value]}`,
1351
+ from: ConnectionState[oldState],
1352
+ duration,
1286
1353
  durationFromDisconnected,
1287
- reason,
1288
- connectionInitiationReason, pendingClientId: this.connectionStateHandler.pendingClientId, clientId: this.clientId, autoReconnect,
1289
- opsBehind, online: OnlineStatus[isOnline()], lastVisible: this.lastVisible !== undefined
1354
+ reason: reason?.text,
1355
+ connectionInitiationReason,
1356
+ pendingClientId: this.connectionStateHandler.pendingClientId,
1357
+ clientId: this.clientId,
1358
+ autoReconnect,
1359
+ opsBehind,
1360
+ online: OnlineStatus[isOnline()],
1361
+ lastVisible: this.lastVisible !== undefined
1290
1362
  ? performance.now() - this.lastVisible
1291
- : undefined, checkpointSequenceNumber, quorumSize: (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum.getMembers().size }, this._deltaManager.connectionProps), error);
1363
+ : undefined,
1364
+ checkpointSequenceNumber,
1365
+ quorumSize: this._protocolHandler?.quorum.getMembers().size,
1366
+ isDirty: this.isDirty,
1367
+ ...this._deltaManager.connectionProps,
1368
+ }, reason?.error);
1292
1369
  if (value === ConnectionState.Connected) {
1293
1370
  this.firstConnection = false;
1294
1371
  }
1295
1372
  }
1296
1373
  propagateConnectionState(initialTransition, disconnectedReason) {
1297
- var _a;
1298
1374
  // When container loaded, we want to propagate initial connection state.
1299
1375
  // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1300
1376
  // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
@@ -1304,22 +1380,10 @@ export class Container extends EventEmitterWithErrorHandling {
1304
1380
  return;
1305
1381
  }
1306
1382
  const state = this.connectionState === ConnectionState.Connected;
1307
- const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
1308
- !this.firstConnection &&
1309
- this.connectionMode === "write";
1310
- if (logOpsOnReconnect) {
1311
- this.messageCountAfterDisconnection = 0;
1312
- }
1313
1383
  // Both protocol and context should not be undefined if we got so far.
1314
- this.setContextConnectedState(state, (_a = this.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1384
+ this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
1315
1385
  this.protocolHandler.setConnectionState(state, this.clientId);
1316
- raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
1317
- if (logOpsOnReconnect) {
1318
- this.mc.logger.sendTelemetryEvent({
1319
- eventName: "OpsSentOnReconnect",
1320
- count: this.messageCountAfterDisconnection,
1321
- });
1322
- }
1386
+ raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
1323
1387
  }
1324
1388
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1325
1389
  submitContainerMessage(type, contents, batch, metadata) {
@@ -1357,13 +1421,11 @@ export class Container extends EventEmitterWithErrorHandling {
1357
1421
  return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
1358
1422
  }
1359
1423
  submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
1360
- var _a;
1361
1424
  if (this.connectionState !== ConnectionState.Connected) {
1362
1425
  this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
1363
1426
  return -1;
1364
1427
  }
1365
- this.messageCountAfterDisconnection += 1;
1366
- (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
1428
+ this.noopHeuristic?.notifyMessageSent();
1367
1429
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1368
1430
  }
1369
1431
  processRemoteMessage(message) {
@@ -1371,23 +1433,6 @@ export class Container extends EventEmitterWithErrorHandling {
1371
1433
  this.savedOps.push(message);
1372
1434
  }
1373
1435
  const local = this.clientId === message.clientId;
1374
- // Check and report if we're getting messages from a clientId that we previously
1375
- // flagged should have left, or from a client that's not in the quorum but should be
1376
- if (message.clientId != null) {
1377
- const client = this.protocolHandler.quorum.getMember(message.clientId);
1378
- if (client === undefined && message.type !== MessageType.ClientJoin) {
1379
- // pre-0.58 error message: messageClientIdMissingFromQuorum
1380
- throw new Error("Remote message's clientId is missing from the quorum");
1381
- }
1382
- // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
1383
- // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
1384
- // document we don't need to blow up aggressively.
1385
- if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
1386
- !canBeCoalescedByService(message)) {
1387
- // pre-0.58 error message: messageClientIdShouldHaveLeft
1388
- throw new Error("Remote message's clientId already should have left");
1389
- }
1390
- }
1391
1436
  // Allow the protocol handler to process the message
1392
1437
  const result = this.protocolHandler.processMessage(message, local);
1393
1438
  // Forward messages to the loaded runtime for processing
@@ -1438,8 +1483,7 @@ export class Container extends EventEmitterWithErrorHandling {
1438
1483
  * @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
1439
1484
  */
1440
1485
  async fetchSnapshotTree(specifiedVersion) {
1441
- var _a;
1442
- const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
1486
+ const version = await this.getVersion(specifiedVersion ?? null);
1443
1487
  if (version === undefined && specifiedVersion !== undefined) {
1444
1488
  // We should have a defined version to load from if specified version requested
1445
1489
  this.mc.logger.sendErrorEvent({
@@ -1448,22 +1492,14 @@ export class Container extends EventEmitterWithErrorHandling {
1448
1492
  });
1449
1493
  }
1450
1494
  this._loadedFromVersion = version;
1451
- const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
1495
+ const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
1452
1496
  if (snapshot === undefined && version !== undefined) {
1453
1497
  this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
1454
1498
  }
1455
- return { snapshot, versionId: version === null || version === void 0 ? void 0 : version.id };
1499
+ return { snapshot, versionId: version?.id };
1456
1500
  }
1457
- async instantiateContextDetached(existing, snapshot) {
1458
- const codeDetails = this.getCodeDetailsFromQuorum();
1459
- if (codeDetails === undefined) {
1460
- throw new Error("pkg should be provided in create flow!!");
1461
- }
1462
- await this.instantiateContext(existing, codeDetails, snapshot);
1463
- }
1464
- async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1465
- var _a, _b;
1466
- assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
1501
+ async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
1502
+ assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
1467
1503
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1468
1504
  // are set. Global requests will still go directly to the loader
1469
1505
  const maybeLoader = this.scope;
@@ -1474,25 +1510,20 @@ export class Container extends EventEmitterWithErrorHandling {
1474
1510
  // An older interface ICodeLoader could return an IFluidModule which didn't have details.
1475
1511
  // If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
1476
1512
  // TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
1477
- details: (_b = loadCodeResult.details) !== null && _b !== void 0 ? _b : codeDetails,
1513
+ details: loadCodeResult.details ?? codeDetails,
1478
1514
  };
1479
1515
  const fluidExport = this._loadedModule.module.fluidExport;
1480
- const runtimeFactory = fluidExport === null || fluidExport === void 0 ? void 0 : fluidExport.IRuntimeFactory;
1516
+ const runtimeFactory = fluidExport?.IRuntimeFactory;
1481
1517
  if (runtimeFactory === undefined) {
1482
1518
  throw new Error(packageNotFactoryError);
1483
1519
  }
1484
- const getSpecifiedCodeDetails = () => {
1485
- var _a;
1486
- return ((_a = this.protocolHandler.quorum.get("code")) !== null && _a !== void 0 ? _a : this.protocolHandler.quorum.get("code2"));
1487
- };
1488
- const context = new 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, () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; }, () => this.clientId, () => this._deltaManager.serviceConfiguration, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
1489
- this._lifecycleEvents.once("disposed", () => {
1490
- context.dispose();
1491
- });
1520
+ const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
1521
+ this.protocolHandler.quorum.get("code2"));
1522
+ const existing = snapshot !== undefined;
1523
+ const context = new 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);
1492
1524
  this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1493
1525
  this._lifecycleEvents.emit("runtimeInstantiated");
1494
1526
  this._loadedCodeDetails = codeDetails;
1495
- this.emit("contextChanged", codeDetails);
1496
1527
  }
1497
1528
  /**
1498
1529
  * Set the connected state of the ContainerContext
@@ -1501,8 +1532,7 @@ export class Container extends EventEmitterWithErrorHandling {
1501
1532
  * @param readonly - Is the container in readonly mode?
1502
1533
  */
1503
1534
  setContextConnectedState(state, readonly) {
1504
- var _a;
1505
- if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1535
+ if (this._runtime?.disposed === false) {
1506
1536
  /**
1507
1537
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1508
1538
  * ops getting through to the DeltaManager.
@@ -1512,5 +1542,25 @@ export class Container extends EventEmitterWithErrorHandling {
1512
1542
  this.runtime.setConnectionState(state && !readonly, this.clientId);
1513
1543
  }
1514
1544
  }
1545
+ handleDeltaConnectionArg(connectionArgs, deltaConnectionArg, canConnect = true) {
1546
+ switch (deltaConnectionArg) {
1547
+ case undefined:
1548
+ if (canConnect) {
1549
+ // connect to delta stream now since we did not before
1550
+ this.connectToDeltaStream(connectionArgs);
1551
+ }
1552
+ // intentional fallthrough
1553
+ case "delayed":
1554
+ assert(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
1555
+ this.inboundQueuePausedFromInit = false;
1556
+ this._deltaManager.inbound.resume();
1557
+ this._deltaManager.inboundSignal.resume();
1558
+ break;
1559
+ case "none":
1560
+ break;
1561
+ default:
1562
+ unreachableCase(deltaConnectionArg);
1563
+ }
1564
+ }
1515
1565
  }
1516
1566
  //# sourceMappingURL=container.js.map