@fluidframework/container-loader 2.0.0-dev.5.3.2.178189 → 2.0.0-dev.6.4.0.191457

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/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");
@@ -54,7 +55,7 @@ const packageNotFactoryError = "Code package does not implement IRuntimeFactory"
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,13 +160,12 @@ 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
166
  this.clientsWhoShouldHaveLeft = new Set();
168
- this.setAutoReconnectTime = common_utils_1.performance.now();
169
- this._lifecycleEvents = new common_utils_1.TypedEventEmitter();
167
+ this.setAutoReconnectTime = client_utils_1.performance.now();
168
+ this._lifecycleEvents = new client_utils_1.TypedEventEmitter();
170
169
  this._disposed = false;
171
170
  this.getAbsoluteUrl = async (relativeUrl) => {
172
171
  if (this.resolvedUrl === undefined) {
@@ -182,9 +181,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
182
181
  this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
183
182
  };
184
183
  const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
185
- this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected] = common_utils_1.performance.now();
186
- const pendingLocalState = loadProps === null || loadProps === void 0 ? void 0 : loadProps.pendingLocalState;
187
- 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;
188
188
  this.clientDetailsOverride = clientDetailsOverride;
189
189
  this.urlResolver = urlResolver;
190
190
  this.serviceFactory = documentServiceFactory;
@@ -192,14 +192,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
192
192
  // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
193
193
  // all clients that were loaded from the same loader (including summarizer clients).
194
194
  // Tracking alternative ways to handle this in AB#4129.
195
- this.options = Object.assign({}, options);
195
+ this.options = { ...options };
196
196
  this.scope = scope;
197
197
  this.detachedBlobStorage = detachedBlobStorage;
198
198
  this.protocolHandlerBuilder =
199
- 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)));
200
201
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
201
202
  this.clone = async (_loadProps, createParamOverrides) => {
202
- return Container.load(_loadProps, Object.assign(Object.assign({}, createProps), createParamOverrides));
203
+ return Container.load(_loadProps, {
204
+ ...createProps,
205
+ ...createParamOverrides,
206
+ });
203
207
  };
204
208
  // Create logger for data stores to use
205
209
  const type = this.client.details.type;
@@ -207,50 +211,50 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
207
211
  const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
208
212
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
209
213
  // We assign the id later so property getter is used.
210
- this.subLogger = telemetry_utils_1.ChildLogger.create(subLogger, undefined, {
211
- all: {
212
- clientType,
213
- containerId: (0, uuid_1.v4)(),
214
- docId: () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; },
215
- containerAttachState: () => this._attachState,
216
- containerLifecycleState: () => this._lifecycleState,
217
- containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
218
- serializedContainer: pendingLocalState !== undefined,
219
- },
220
- // we need to be judicious with our logging here to avoid generating too much data
221
- // all data logged here should be broadly applicable, and not specific to a
222
- // specific error or class of errors
223
- error: {
224
- // load information to associate errors with the specific load point
225
- dmInitialSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.initialSequenceNumber; },
226
- dmLastProcessedSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastSequenceNumber; },
227
- dmLastKnownSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastKnownSeqNumber; },
228
- containerLoadedFromVersionId: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
229
- containerLoadedFromVersionDate: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
230
- // message information to associate errors with the specific execution state
231
- // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
232
- 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; },
233
- 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; },
234
- dmLastMsqSeqClientId: () => {
235
- var _a, _b, _c, _d;
236
- return ((_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId) === null
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
237
241
  ? "null"
238
- : (_d = (_c = this.deltaManager) === null || _c === void 0 ? void 0 : _c.lastMessage) === null || _d === void 0 ? void 0 : _d.clientId;
242
+ : this.deltaManager?.lastMessage?.clientId,
243
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
244
+ connectionStateDuration: () => client_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
239
245
  },
240
- 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; },
241
- connectionStateDuration: () => common_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
242
246
  },
243
247
  });
244
248
  // Prefix all events in this file with container-loader
245
- 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" });
246
250
  this._deltaManager = this.createDeltaManager();
247
251
  this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
248
252
  logger: this.mc.logger,
249
- connectionStateChanged: (value, oldState, reason, error) => {
253
+ connectionStateChanged: (value, oldState, reason) => {
250
254
  if (value === connectionState_1.ConnectionState.Connected) {
251
255
  this._clientId = this.connectionStateHandler.pendingClientId;
252
256
  }
253
- this.logConnectionStateChangeTelemetry(value, oldState, reason, error);
257
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
254
258
  if (this._lifecycleState === "loaded") {
255
259
  this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
256
260
  ? reason
@@ -266,9 +270,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
266
270
  // Report issues only if we already loaded container - op processing is paused while container is loading,
267
271
  // so we always time-out processing of join op in cases where fetching snapshot takes a minute.
268
272
  // It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
269
- this._deltaManager.logConnectionIssue(Object.assign({ eventName,
270
- mode, category: this._lifecycleState === "loading" ? "generic" : category, duration: common_utils_1.performance.now() -
271
- 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
+ });
272
281
  // If this is "write" connection, it took too long to receive join op. But in most cases that's due
273
282
  // to very slow op fetches and we will eventually get there.
274
283
  // For "read" connections, we get here due to self join signal not arriving on time. We will need to
@@ -278,14 +287,15 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
278
287
  // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
279
288
  // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
280
289
  if (mode === "read") {
281
- this.disconnect();
282
- this.connect();
290
+ const reason = { text: "NoJoinSignal" };
291
+ this.disconnectInternal(reason);
292
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
283
293
  }
284
294
  },
285
295
  clientShouldHaveLeft: (clientId) => {
286
296
  this.clientsWhoShouldHaveLeft.add(clientId);
287
297
  },
288
- }, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
298
+ }, this.deltaManager, pendingLocalState?.clientId);
289
299
  this.on(savedContainerEvent, () => {
290
300
  this.connectionStateHandler.containerSaved();
291
301
  });
@@ -294,21 +304,22 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
294
304
  // using this callback and fix them up.
295
305
  const addProtocolSummaryIfMissing = (summaryTree) => (0, driver_utils_1.isCombinedAppAndProtocolSummary)(summaryTree) === true
296
306
  ? summaryTree
297
- : (0, driver_utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
307
+ : (0, utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
298
308
  // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
299
309
  // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
300
- const forceEnableSummarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _a !== void 0 ? _a : options.summarizeProtocolTree;
301
- 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);
302
313
  const isDomAvailable = typeof document === "object" &&
303
314
  document !== null &&
304
315
  typeof document.addEventListener === "function" &&
305
316
  document.addEventListener !== null;
306
- // keep track of last time page was visible for telemetry
307
- if (isDomAvailable) {
308
- 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;
309
320
  this.visibilityEventHandler = () => {
310
321
  if (document.hidden) {
311
- this.lastVisible = common_utils_1.performance.now();
322
+ this.lastVisible = client_utils_1.performance.now();
312
323
  }
313
324
  else {
314
325
  // settimeout so this will hopefully fire after disconnect event if being hidden caused it
@@ -325,7 +336,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
325
336
  * @internal
326
337
  */
327
338
  static async load(loadProps, createProps) {
328
- const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
339
+ const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } = loadProps;
329
340
  const container = new Container(createProps, loadProps);
330
341
  const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
331
342
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
@@ -333,26 +344,31 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
333
344
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
334
345
  // to return container, so ignore this value and use undefined for opsBeforeReturn
335
346
  const mode = pendingLocalState
336
- ? 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;
337
349
  const onClosed = (err) => {
338
350
  // pre-0.58 error message: containerClosedWithoutErrorDuringLoad
339
- 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"));
340
352
  };
341
353
  container.on("closed", onClosed);
342
354
  container
343
- .load(version, mode, resolvedUrl, pendingLocalState)
355
+ .load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
344
356
  .finally(() => {
345
357
  container.removeListener("closed", onClosed);
346
358
  })
347
359
  .then((props) => {
348
- event.end(Object.assign(Object.assign({}, props), loadMode));
360
+ event.end({ ...props, ...loadMode });
349
361
  resolve(container);
350
362
  }, (error) => {
351
363
  const err = (0, telemetry_utils_1.normalizeError)(error);
352
364
  // Depending where error happens, we can be attempting to connect to web socket
353
365
  // and continuously retrying (consider offline mode)
354
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.
355
370
  container.close(err);
371
+ container.dispose(err);
356
372
  onClosed(err);
357
373
  });
358
374
  }), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
@@ -389,10 +405,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
389
405
  }
390
406
  }
391
407
  get closed() {
392
- return (this._lifecycleState === "closing" ||
393
- this._lifecycleState === "closed" ||
394
- this._lifecycleState === "disposing" ||
395
- this._lifecycleState === "disposed");
408
+ return (this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed);
409
+ }
410
+ get disposed() {
411
+ return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
396
412
  }
397
413
  get runtime() {
398
414
  if (this._runtime === undefined) {
@@ -413,7 +429,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
413
429
  return this;
414
430
  }
415
431
  get resolvedUrl() {
416
- var _a;
417
432
  /**
418
433
  * All attached containers will have a document service,
419
434
  * this is required, as attached containers are attached to
@@ -425,7 +440,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
425
440
  * is always the same as the containers, as we had to
426
441
  * obtain the resolved url, and then create the service from it.
427
442
  */
428
- return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
443
+ return this.service?.resolvedUrl;
429
444
  }
430
445
  get readOnlyInfo() {
431
446
  return this._deltaManager.readOnlyInfo;
@@ -453,8 +468,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
453
468
  return this._clientId;
454
469
  }
455
470
  get offlineLoadEnabled() {
456
- var _a, _b;
457
- 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;
458
473
  // summarizer will not have any pending state we want to save
459
474
  return enabled && this.deltaManager.clientDetails.capabilities.interactive;
460
475
  }
@@ -491,18 +506,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
491
506
  * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
492
507
  */
493
508
  async getEntryPoint() {
494
- var _a, _b;
495
509
  if (this._disposed) {
496
- throw new container_utils_1.UsageError("The context is already disposed");
510
+ throw new telemetry_utils_1.UsageError("The context is already disposed");
497
511
  }
498
512
  if (this._runtime !== undefined) {
499
- return (_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a);
513
+ return this._runtime.getEntryPoint?.();
500
514
  }
501
515
  return new Promise((resolve, reject) => {
502
516
  const runtimeInstantiatedHandler = () => {
503
- var _a, _b;
504
- (0, common_utils_1.assert)(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
505
- resolve((_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
517
+ (0, core_utils_1.assert)(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
518
+ resolve(this._runtime.getEntryPoint?.());
506
519
  this._lifecycleEvents.off("disposed", disposedHandler);
507
520
  };
508
521
  const disposedHandler = () => {
@@ -532,12 +545,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
532
545
  this.verifyClosed();
533
546
  }
534
547
  verifyClosed() {
535
- (0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
536
- (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 */);
537
550
  }
538
551
  closeCore(error) {
539
- var _a;
540
- (0, common_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
552
+ (0, core_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
541
553
  try {
542
554
  // Ensure that we raise all key events even if one of these throws
543
555
  try {
@@ -552,7 +564,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
552
564
  : "generic",
553
565
  }, error);
554
566
  this._lifecycleState = "closing";
555
- (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
567
+ this._protocolHandler?.close();
556
568
  this.connectionStateHandler.dispose();
557
569
  }
558
570
  catch (exception) {
@@ -572,8 +584,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
572
584
  }
573
585
  }
574
586
  disposeCore(error) {
575
- var _a, _b, _c;
576
- (0, common_utils_1.assert)(!this._disposed, 0x54c /* Container already disposed */);
587
+ (0, core_utils_1.assert)(!this._disposed, 0x54c /* Container already disposed */);
577
588
  this._disposed = true;
578
589
  try {
579
590
  // Ensure that we raise all key events even if one of these throws
@@ -589,15 +600,15 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
589
600
  if (this._lifecycleState !== "closed") {
590
601
  this._lifecycleState = "disposing";
591
602
  }
592
- (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
603
+ this._protocolHandler?.close();
593
604
  this.connectionStateHandler.dispose();
594
605
  const maybeError = error !== undefined ? new Error(error.message) : undefined;
595
- (_b = this._runtime) === null || _b === void 0 ? void 0 : _b.dispose(maybeError);
606
+ this._runtime?.dispose(maybeError);
596
607
  this.storageAdapter.dispose();
597
608
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
598
609
  // about file, like file being overwritten in storage, but client having stale local cache.
599
610
  // Driver need to ensure all caches are cleared on critical errors
600
- (_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
611
+ this.service?.dispose(error);
601
612
  }
602
613
  catch (exception) {
603
614
  this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
@@ -613,45 +624,57 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
613
624
  this._lifecycleEvents.emit("disposed");
614
625
  }
615
626
  }
616
- closeAndGetPendingLocalState() {
627
+ async closeAndGetPendingLocalState() {
617
628
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
618
629
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
619
630
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
620
- 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 });
621
633
  this.close();
622
634
  return pendingState;
623
635
  }
624
- getPendingLocalState() {
625
- if (!this.offlineLoadEnabled) {
626
- throw new container_utils_1.UsageError("Can't get pending local state unless offline load is enabled");
627
- }
628
- if (this.closed || this._disposed) {
629
- throw new container_utils_1.UsageError("Pending state cannot be retried if the container is closed or disposed");
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.runtime.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" */);
674
+ (0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
652
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,27 +683,26 @@ 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
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.
@@ -698,7 +720,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
698
720
  // Actually go and create the resolved document
699
721
  if (this.service === undefined) {
700
722
  const createNewResolvedUrl = await this.urlResolver.resolve(request);
701
- (0, common_utils_1.assert)(this.client.details.type !== summarizerClientType &&
723
+ (0, core_utils_1.assert)(this.client.details.type !== summarizerClientType &&
702
724
  createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
703
725
  this.service = await (0, driver_utils_1.runWithRetry)(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
704
726
  cancel: this._deltaManager.closeAbortController.signal,
@@ -707,7 +729,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
707
729
  await this.storageAdapter.connectToService(this.service);
708
730
  if (hasAttachmentBlobs) {
709
731
  // upload blobs to storage
710
- (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" */);
711
733
  // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
712
734
  // support blob handles that only know about the local IDs
713
735
  const redirectTable = new Map();
@@ -725,7 +747,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
725
747
  // take summary and upload
726
748
  const appSummary = this.runtime.createSummary(redirectTable);
727
749
  const protocolSummary = this.captureProtocolSummary();
728
- summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
750
+ summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
729
751
  this._attachState = container_definitions_1.AttachState.Attaching;
730
752
  this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
731
753
  this.emit("attaching");
@@ -745,16 +767,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
745
767
  this.runtime.setAttachState(container_definitions_1.AttachState.Attached);
746
768
  this.emit("attached");
747
769
  if (!this.closed) {
748
- this.resumeInternal({
770
+ this.handleDeltaConnectionArg({
749
771
  fetchOpsFromStorage: false,
750
- reason: "createDetached",
751
- });
772
+ reason: { text: "createDetached" },
773
+ }, attachProps?.deltaConnection);
752
774
  }
753
775
  }
754
776
  catch (error) {
755
777
  // add resolved URL on error object so that host has the ability to find this document and delete it
756
778
  const newError = (0, telemetry_utils_1.normalizeError)(error);
757
- newError.addTelemetryProperties({ resolvedUrl: (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.url });
779
+ newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
758
780
  this.close(newError);
759
781
  throw newError;
760
782
  }
@@ -763,12 +785,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
763
785
  async request(path) {
764
786
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
765
787
  }
766
- setAutoReconnectInternal(mode) {
788
+ setAutoReconnectInternal(mode, reason) {
767
789
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
768
790
  if (currentMode === mode) {
769
791
  return;
770
792
  }
771
- const now = common_utils_1.performance.now();
793
+ const now = client_utils_1.performance.now();
772
794
  const duration = now - this.setAutoReconnectTime;
773
795
  this.setAutoReconnectTime = now;
774
796
  this.mc.logger.sendTelemetryEvent({
@@ -777,47 +799,50 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
777
799
  connectionState: connectionState_1.ConnectionState[this.connectionState],
778
800
  duration,
779
801
  });
780
- this._deltaManager.connectionManager.setAutoReconnect(mode);
802
+ this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
781
803
  }
782
804
  connect() {
783
805
  if (this.closed) {
784
- 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`);
785
807
  }
786
808
  else if (this._attachState !== container_definitions_1.AttachState.Attached) {
787
- 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`);
788
810
  }
789
811
  else if (!this.connected) {
790
812
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
791
813
  // If there is gap, we will learn about it once connected, but the gap should be small (if any),
792
814
  // assuming that connect() is called quickly after initial container boot.
793
- this.connectInternal({ reason: "DocumentConnect", fetchOpsFromStorage: false });
815
+ this.connectInternal({
816
+ reason: { text: "DocumentConnect" },
817
+ fetchOpsFromStorage: false,
818
+ });
794
819
  }
795
820
  }
796
821
  connectInternal(args) {
797
- (0, common_utils_1.assert)(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
798
- (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" */);
799
824
  // Resume processing ops and connect to delta stream
800
825
  this.resumeInternal(args);
801
826
  // Set Auto Reconnect Mode
802
827
  const mode = contracts_1.ReconnectMode.Enabled;
803
- this.setAutoReconnectInternal(mode);
828
+ this.setAutoReconnectInternal(mode, args.reason);
804
829
  }
805
830
  disconnect() {
806
831
  if (this.closed) {
807
- 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`);
808
833
  }
809
834
  else {
810
- this.disconnectInternal();
835
+ this.disconnectInternal({ text: "DocumentDisconnect" });
811
836
  }
812
837
  }
813
- disconnectInternal() {
814
- (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" */);
815
840
  // Set Auto Reconnect Mode
816
841
  const mode = contracts_1.ReconnectMode.Disabled;
817
- this.setAutoReconnectInternal(mode);
842
+ this.setAutoReconnectInternal(mode, reason);
818
843
  }
819
844
  resumeInternal(args) {
820
- (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" */);
821
846
  // Resume processing ops
822
847
  if (this.inboundQueuePausedFromInit) {
823
848
  this.inboundQueuePausedFromInit = false;
@@ -854,14 +879,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
854
879
  return;
855
880
  }
856
881
  // pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
857
- 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");
858
883
  this.close(error);
859
884
  }
860
885
  /**
861
886
  * Determines if the currently loaded module satisfies the incoming constraint code details
862
887
  */
863
888
  async satisfies(constraintCodeDetails) {
864
- var _a, _b;
865
889
  // If we have no module, it can't satisfy anything.
866
890
  if (this._loadedModule === undefined) {
867
891
  return false;
@@ -871,8 +895,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
871
895
  if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
872
896
  comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
873
897
  }
874
- const maybeCompareExport = (_a = this._loadedModule) === null || _a === void 0 ? void 0 : _a.module.fluidExport;
875
- if ((maybeCompareExport === null || maybeCompareExport === void 0 ? void 0 : maybeCompareExport.IFluidCodeDetailsComparer) !== undefined) {
898
+ const maybeCompareExport = this._loadedModule?.module.fluidExport;
899
+ if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
876
900
  comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
877
901
  }
878
902
  // If there are no comparers, then it's impossible to know if the currently loaded package satisfies
@@ -882,7 +906,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
882
906
  return false;
883
907
  }
884
908
  for (const comparer of comparers) {
885
- const satisfies = await comparer.satisfies((_b = this._loadedModule) === null || _b === void 0 ? void 0 : _b.details, constraintCodeDetails);
909
+ const satisfies = await comparer.satisfies(this._loadedModule?.details, constraintCodeDetails);
886
910
  if (satisfies === false) {
887
911
  return false;
888
912
  }
@@ -905,8 +929,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
905
929
  *
906
930
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
907
931
  */
908
- async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
909
- var _a, _b, _c;
932
+ async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
933
+ const timings = { phase1: client_utils_1.performance.now() };
910
934
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
911
935
  // Ideally we always connect as "read" by default.
912
936
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
@@ -918,7 +942,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
918
942
  // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
919
943
  // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
920
944
  const connectionArgs = {
921
- reason: "DocumentOpen",
945
+ reason: { text: "DocumentOpen" },
922
946
  mode: "write",
923
947
  fetchOpsFromStorage: false,
924
948
  };
@@ -937,6 +961,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
937
961
  });
938
962
  }
939
963
  this._attachState = container_definitions_1.AttachState.Attached;
964
+ timings.phase2 = client_utils_1.performance.now();
940
965
  // Fetch specified snapshot.
941
966
  const { snapshot, versionId } = pendingLocalState === undefined
942
967
  ? await this.fetchSnapshotTree(specifiedVersion)
@@ -946,7 +971,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
946
971
  this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
947
972
  }
948
973
  else {
949
- (0, common_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
974
+ (0, core_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
950
975
  if (this.offlineLoadEnabled) {
951
976
  this.baseSnapshot = snapshot;
952
977
  // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
@@ -955,9 +980,51 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
955
980
  }
956
981
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
957
982
  // If we saved ops, we will replay them and don't need DeltaManager to fetch them
958
- const sequenceNumber = (_a = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.savedOps[pendingLocalState.savedOps.length - 1]) === null || _a === void 0 ? void 0 : _a.sequenceNumber;
959
- 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;
960
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
+ }
961
1028
  // Attach op handlers to finish initialization and be able to start processing ops
962
1029
  // Kick off any ops fetching if required.
963
1030
  switch (loadMode.opsBeforeReturn) {
@@ -966,32 +1033,30 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
966
1033
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
967
1034
  this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
968
1035
  break;
1036
+ case "sequenceNumber":
969
1037
  case "cached":
970
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
971
- break;
972
1038
  case "all":
973
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
1039
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, loadMode.opsBeforeReturn);
974
1040
  break;
975
1041
  default:
976
- (0, common_utils_1.unreachableCase)(loadMode.opsBeforeReturn);
1042
+ (0, core_utils_1.unreachableCase)(loadMode.opsBeforeReturn);
977
1043
  }
978
1044
  // ...load in the existing quorum
979
1045
  // Initialize the protocol handler
980
1046
  await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1047
+ timings.phase3 = client_utils_1.performance.now();
981
1048
  const codeDetails = this.getCodeDetailsFromQuorum();
982
- await this.instantiateContext(true, // existing
983
- 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);
984
1052
  // replay saved ops
985
1053
  if (pendingLocalState) {
986
1054
  for (const message of pendingLocalState.savedOps) {
987
1055
  this.processRemoteMessage(message);
988
1056
  // allow runtime to apply stashed ops at this op's sequence number
989
- await ((_c = (_b = this.runtime).notifyOpReplay) === null || _c === void 0 ? void 0 : _c.call(_b, message));
1057
+ await this.runtime.notifyOpReplay?.(message);
990
1058
  }
991
1059
  pendingLocalState.savedOps = [];
992
- // now set clientId to stashed clientId so live ops are correctly processed as local
993
- (0, common_utils_1.assert)(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
994
- this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
995
1060
  }
996
1061
  // We might have hit some failure that did not manifest itself in exception in this flow,
997
1062
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
@@ -1003,24 +1068,20 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1003
1068
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1004
1069
  this._deltaManager.inbound.pause();
1005
1070
  }
1006
- switch (loadMode.deltaConnection) {
1007
- case undefined:
1008
- if (pendingLocalState) {
1009
- // connect to delta stream now since we did not before
1010
- 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);
1011
1081
  }
1012
- // intentional fallthrough
1013
- case "delayed":
1014
- (0, common_utils_1.assert)(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
1015
- this.inboundQueuePausedFromInit = false;
1016
- this._deltaManager.inbound.resume();
1017
- this._deltaManager.inboundSignal.resume();
1018
- break;
1019
- case "none":
1020
- break;
1021
- default:
1022
- (0, common_utils_1.unreachableCase)(loadMode.deltaConnection);
1023
- }
1082
+ };
1083
+ this.on("op", opHandler);
1084
+ });
1024
1085
  }
1025
1086
  // Safety net: static version of Container.load() should have learned about it through "closed" handler.
1026
1087
  // But if that did not happen for some reason, fail load for sure.
@@ -1032,6 +1093,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1032
1093
  }
1033
1094
  // Internal context is fully loaded at this point
1034
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);
1035
1101
  return {
1036
1102
  sequenceNumber: attributes.sequenceNumber,
1037
1103
  version: versionId,
@@ -1039,7 +1105,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1039
1105
  dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
1040
1106
  };
1041
1107
  }
1042
- async createDetached(source) {
1108
+ async createDetached(codeDetails) {
1043
1109
  const attributes = {
1044
1110
  sequenceNumber: detachedContainerRefSeqNumber,
1045
1111
  term: protocol_1.OnlyValidTermValue,
@@ -1047,19 +1113,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1047
1113
  };
1048
1114
  await this.attachDeltaManagerOpHandler(attributes);
1049
1115
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1050
- const qValues = (0, quorum_1.initQuorumValuesFromCodeDetails)(source);
1116
+ const qValues = (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails);
1051
1117
  this.initializeProtocolState(attributes, {
1052
1118
  members: [],
1053
1119
  proposals: [],
1054
1120
  values: qValues,
1055
1121
  });
1056
- // The load context - given we seeded the quorum - will be great
1057
- await this.instantiateContextDetached(false);
1122
+ await this.instantiateRuntime(codeDetails, undefined);
1058
1123
  this.setLoaded();
1059
1124
  }
1060
1125
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
1061
1126
  if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
1062
- (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" */);
1063
1128
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
1064
1129
  }
1065
1130
  const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
@@ -1069,14 +1134,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1069
1134
  // Initialize the protocol handler
1070
1135
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
1071
1136
  const qValues = await (0, driver_utils_1.readAndParse)(this.storageAdapter, baseTree.blobs.quorumValues);
1072
- const codeDetails = (0, quorum_1.getCodeDetailsFromQuorumValues)(qValues);
1073
1137
  this.initializeProtocolState(attributes, {
1074
1138
  members: [],
1075
1139
  proposals: [],
1076
- values: codeDetails !== undefined ? (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails) : [],
1140
+ values: qValues,
1077
1141
  });
1078
- await this.instantiateContextDetached(true, // existing
1079
- snapshotTree);
1142
+ const codeDetails = this.getCodeDetailsFromQuorum();
1143
+ await this.instantiateRuntime(codeDetails, snapshotTree);
1080
1144
  this.setLoaded();
1081
1145
  }
1082
1146
  async getDocumentAttributes(storage, tree) {
@@ -1113,7 +1177,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1113
1177
  }
1114
1178
  initializeProtocolState(attributes, quorumSnapshot) {
1115
1179
  const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, JSON.stringify({ key, value })));
1116
- 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
+ });
1117
1184
  protocol.quorum.on("error", (error) => {
1118
1185
  protocolLogger.sendErrorEvent(error);
1119
1186
  });
@@ -1175,8 +1242,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1175
1242
  return pkg;
1176
1243
  }
1177
1244
  get client() {
1178
- var _a;
1179
- const client = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.client) !== undefined
1245
+ const client = this.options?.client !== undefined
1180
1246
  ? this.options.client
1181
1247
  : {
1182
1248
  details: {
@@ -1207,14 +1273,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1207
1273
  }
1208
1274
  createDeltaManager() {
1209
1275
  const serviceProvider = () => this.service;
1210
- 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));
1211
1277
  // Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
1212
1278
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1213
1279
  deltaManager.inbound.pause();
1214
1280
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1215
1281
  deltaManager.inboundSignal.pause();
1216
1282
  deltaManager.on("connect", (details, _opsBehind) => {
1217
- (0, common_utils_1.assert)(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
1283
+ (0, core_utils_1.assert)(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
1218
1284
  this.connectionStateHandler.receivedConnectEvent(details);
1219
1285
  });
1220
1286
  deltaManager.on("establishingConnection", (reason) => {
@@ -1223,11 +1289,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1223
1289
  deltaManager.on("cancelEstablishingConnection", (reason) => {
1224
1290
  this.connectionStateHandler.cancelEstablishingConnection(reason);
1225
1291
  });
1226
- deltaManager.on("disconnect", (reason, error) => {
1227
- var _a;
1228
- (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
1292
+ deltaManager.on("disconnect", (reason) => {
1293
+ this.noopHeuristic?.notifyDisconnect();
1229
1294
  if (!this.closed) {
1230
- this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1295
+ this.connectionStateHandler.receivedDisconnectEvent(reason);
1231
1296
  }
1232
1297
  });
1233
1298
  deltaManager.on("throttled", (warning) => {
@@ -1259,10 +1324,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1259
1324
  },
1260
1325
  }, prefetchType);
1261
1326
  }
1262
- logConnectionStateChangeTelemetry(value, oldState, reason, error) {
1263
- var _a;
1327
+ logConnectionStateChangeTelemetry(value, oldState, reason) {
1264
1328
  // Log actual event
1265
- const time = common_utils_1.performance.now();
1329
+ const time = client_utils_1.performance.now();
1266
1330
  this.connectionTransitionTimes[value] = time;
1267
1331
  const duration = time - this.connectionTransitionTimes[oldState];
1268
1332
  let durationFromDisconnected;
@@ -1277,7 +1341,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1277
1341
  if (value === connectionState_1.ConnectionState.Connected) {
1278
1342
  durationFromDisconnected =
1279
1343
  time - this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected];
1280
- durationFromDisconnected = telemetry_utils_1.TelemetryLogger.formatTick(durationFromDisconnected);
1344
+ durationFromDisconnected = (0, telemetry_utils_1.formatTick)(durationFromDisconnected);
1281
1345
  }
1282
1346
  else if (value === connectionState_1.ConnectionState.CatchingUp) {
1283
1347
  // This info is of most interesting while Catching Up.
@@ -1290,19 +1354,31 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1290
1354
  }
1291
1355
  connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1292
1356
  }
1293
- 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,
1294
1361
  durationFromDisconnected,
1295
- reason,
1296
- connectionInitiationReason, pendingClientId: this.connectionStateHandler.pendingClientId, clientId: this.clientId, autoReconnect,
1297
- opsBehind, online: driver_utils_1.OnlineStatus[(0, driver_utils_1.isOnline)()], lastVisible: this.lastVisible !== undefined
1298
- ? common_utils_1.performance.now() - this.lastVisible
1299
- : 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);
1300
1377
  if (value === connectionState_1.ConnectionState.Connected) {
1301
1378
  this.firstConnection = false;
1302
1379
  }
1303
1380
  }
1304
1381
  propagateConnectionState(initialTransition, disconnectedReason) {
1305
- var _a;
1306
1382
  // When container loaded, we want to propagate initial connection state.
1307
1383
  // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1308
1384
  // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
@@ -1312,22 +1388,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1312
1388
  return;
1313
1389
  }
1314
1390
  const state = this.connectionState === connectionState_1.ConnectionState.Connected;
1315
- const logOpsOnReconnect = this.connectionState === connectionState_1.ConnectionState.Connected &&
1316
- !this.firstConnection &&
1317
- this.connectionMode === "write";
1318
- if (logOpsOnReconnect) {
1319
- this.messageCountAfterDisconnection = 0;
1320
- }
1321
1391
  // Both protocol and context should not be undefined if we got so far.
1322
- this.setContextConnectedState(state, (_a = this.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1392
+ this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
1323
1393
  this.protocolHandler.setConnectionState(state, this.clientId);
1324
- (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason);
1325
- if (logOpsOnReconnect) {
1326
- this.mc.logger.sendTelemetryEvent({
1327
- eventName: "OpsSentOnReconnect",
1328
- count: this.messageCountAfterDisconnection,
1329
- });
1330
- }
1394
+ (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
1331
1395
  }
1332
1396
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1333
1397
  submitContainerMessage(type, contents, batch, metadata) {
@@ -1337,7 +1401,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1337
1401
  case protocol_definitions_1.MessageType.Summarize:
1338
1402
  return this.submitSummaryMessage(contents);
1339
1403
  default: {
1340
- 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 });
1341
1405
  this.close(newError);
1342
1406
  return -1;
1343
1407
  }
@@ -1365,13 +1429,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1365
1429
  return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
1366
1430
  }
1367
1431
  submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
1368
- var _a;
1369
1432
  if (this.connectionState !== connectionState_1.ConnectionState.Connected) {
1370
1433
  this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
1371
1434
  return -1;
1372
1435
  }
1373
- this.messageCountAfterDisconnection += 1;
1374
- (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
1436
+ this.noopHeuristic?.notifyMessageSent();
1375
1437
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1376
1438
  }
1377
1439
  processRemoteMessage(message) {
@@ -1379,23 +1441,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1379
1441
  this.savedOps.push(message);
1380
1442
  }
1381
1443
  const local = this.clientId === message.clientId;
1382
- // Check and report if we're getting messages from a clientId that we previously
1383
- // flagged should have left, or from a client that's not in the quorum but should be
1384
- if (message.clientId != null) {
1385
- const client = this.protocolHandler.quorum.getMember(message.clientId);
1386
- if (client === undefined && message.type !== protocol_definitions_1.MessageType.ClientJoin) {
1387
- // pre-0.58 error message: messageClientIdMissingFromQuorum
1388
- throw new Error("Remote message's clientId is missing from the quorum");
1389
- }
1390
- // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
1391
- // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
1392
- // document we don't need to blow up aggressively.
1393
- if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
1394
- !(0, driver_utils_1.canBeCoalescedByService)(message)) {
1395
- // pre-0.58 error message: messageClientIdShouldHaveLeft
1396
- throw new Error("Remote message's clientId already should have left");
1397
- }
1398
- }
1399
1444
  // Allow the protocol handler to process the message
1400
1445
  const result = this.protocolHandler.processMessage(message, local);
1401
1446
  // Forward messages to the loaded runtime for processing
@@ -1408,13 +1453,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1408
1453
  // That means that if relay service changes settings, such changes will impact only newly booted
1409
1454
  // clients.
1410
1455
  // All existing will continue to use settings they got earlier.
1411
- (0, common_utils_1.assert)(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1456
+ (0, core_utils_1.assert)(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1412
1457
  this.noopHeuristic = new noopHeuristic_1.NoopHeuristic(serviceConfiguration.noopTimeFrequency, serviceConfiguration.noopCountFrequency);
1413
1458
  this.noopHeuristic.on("wantsNoop", () => {
1414
1459
  // On disconnect we notify the heuristic which should prevent it from wanting a noop.
1415
1460
  // Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
1416
1461
  // running the microtask that the heuristic queued in response.
1417
- (0, common_utils_1.assert)(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
1462
+ (0, core_utils_1.assert)(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
1418
1463
  this.submitMessage(protocol_definitions_1.MessageType.NoOp);
1419
1464
  });
1420
1465
  }
@@ -1446,8 +1491,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1446
1491
  * @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
1447
1492
  */
1448
1493
  async fetchSnapshotTree(specifiedVersion) {
1449
- var _a;
1450
- const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
1494
+ const version = await this.getVersion(specifiedVersion ?? null);
1451
1495
  if (version === undefined && specifiedVersion !== undefined) {
1452
1496
  // We should have a defined version to load from if specified version requested
1453
1497
  this.mc.logger.sendErrorEvent({
@@ -1456,22 +1500,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1456
1500
  });
1457
1501
  }
1458
1502
  this._loadedFromVersion = version;
1459
- const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
1503
+ const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
1460
1504
  if (snapshot === undefined && version !== undefined) {
1461
1505
  this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
1462
1506
  }
1463
- return { snapshot, versionId: version === null || version === void 0 ? void 0 : version.id };
1507
+ return { snapshot, versionId: version?.id };
1464
1508
  }
1465
- async instantiateContextDetached(existing, snapshot) {
1466
- const codeDetails = this.getCodeDetailsFromQuorum();
1467
- if (codeDetails === undefined) {
1468
- throw new Error("pkg should be provided in create flow!!");
1469
- }
1470
- await this.instantiateContext(existing, codeDetails, snapshot);
1471
- }
1472
- async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1473
- var _a, _b;
1474
- (0, common_utils_1.assert)(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
1509
+ async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
1510
+ (0, core_utils_1.assert)(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
1475
1511
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1476
1512
  // are set. Global requests will still go directly to the loader
1477
1513
  const maybeLoader = this.scope;
@@ -1482,25 +1518,20 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1482
1518
  // An older interface ICodeLoader could return an IFluidModule which didn't have details.
1483
1519
  // If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
1484
1520
  // TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
1485
- details: (_b = loadCodeResult.details) !== null && _b !== void 0 ? _b : codeDetails,
1521
+ details: loadCodeResult.details ?? codeDetails,
1486
1522
  };
1487
1523
  const fluidExport = this._loadedModule.module.fluidExport;
1488
- const runtimeFactory = fluidExport === null || fluidExport === void 0 ? void 0 : fluidExport.IRuntimeFactory;
1524
+ const runtimeFactory = fluidExport?.IRuntimeFactory;
1489
1525
  if (runtimeFactory === undefined) {
1490
1526
  throw new Error(packageNotFactoryError);
1491
1527
  }
1492
- const getSpecifiedCodeDetails = () => {
1493
- var _a;
1494
- return ((_a = this.protocolHandler.quorum.get("code")) !== null && _a !== void 0 ? _a : this.protocolHandler.quorum.get("code2"));
1495
- };
1496
- 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, () => { 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);
1497
- this._lifecycleEvents.once("disposed", () => {
1498
- context.dispose();
1499
- });
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);
1500
1532
  this._runtime = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1501
1533
  this._lifecycleEvents.emit("runtimeInstantiated");
1502
1534
  this._loadedCodeDetails = codeDetails;
1503
- this.emit("contextChanged", codeDetails);
1504
1535
  }
1505
1536
  /**
1506
1537
  * Set the connected state of the ContainerContext
@@ -1509,8 +1540,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1509
1540
  * @param readonly - Is the container in readonly mode?
1510
1541
  */
1511
1542
  setContextConnectedState(state, readonly) {
1512
- var _a;
1513
- if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1543
+ if (this._runtime?.disposed === false) {
1514
1544
  /**
1515
1545
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1516
1546
  * ops getting through to the DeltaManager.
@@ -1520,6 +1550,26 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1520
1550
  this.runtime.setConnectionState(state && !readonly, this.clientId);
1521
1551
  }
1522
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);
1571
+ }
1572
+ }
1523
1573
  }
1524
1574
  exports.Container = Container;
1525
1575
  //# sourceMappingURL=container.js.map