@fluidframework/container-loader 2.0.0-internal.5.3.2 → 2.0.0-internal.6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +6 -3
  3. package/dist/audience.d.ts +1 -0
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js +3 -1
  6. package/dist/audience.js.map +1 -1
  7. package/dist/connectionManager.d.ts +1 -1
  8. package/dist/connectionManager.d.ts.map +1 -1
  9. package/dist/connectionManager.js +30 -36
  10. package/dist/connectionManager.js.map +1 -1
  11. package/dist/connectionStateHandler.d.ts +2 -1
  12. package/dist/connectionStateHandler.d.ts.map +1 -1
  13. package/dist/connectionStateHandler.js +9 -16
  14. package/dist/connectionStateHandler.js.map +1 -1
  15. package/dist/container.d.ts +12 -8
  16. package/dist/container.d.ts.map +1 -1
  17. package/dist/container.js +199 -156
  18. package/dist/container.js.map +1 -1
  19. package/dist/containerContext.d.ts +2 -12
  20. package/dist/containerContext.d.ts.map +1 -1
  21. package/dist/containerContext.js +1 -20
  22. package/dist/containerContext.js.map +1 -1
  23. package/dist/containerStorageAdapter.js +3 -5
  24. package/dist/containerStorageAdapter.js.map +1 -1
  25. package/dist/contracts.d.ts +11 -2
  26. package/dist/contracts.d.ts.map +1 -1
  27. package/dist/contracts.js +3 -3
  28. package/dist/contracts.js.map +1 -1
  29. package/dist/debugLogger.d.ts +30 -0
  30. package/dist/debugLogger.d.ts.map +1 -0
  31. package/dist/debugLogger.js +95 -0
  32. package/dist/debugLogger.js.map +1 -0
  33. package/dist/deltaManager.d.ts +16 -4
  34. package/dist/deltaManager.d.ts.map +1 -1
  35. package/dist/deltaManager.js +79 -33
  36. package/dist/deltaManager.js.map +1 -1
  37. package/dist/deltaQueue.js +1 -2
  38. package/dist/deltaQueue.js.map +1 -1
  39. package/dist/loader.d.ts +12 -0
  40. package/dist/loader.d.ts.map +1 -1
  41. package/dist/loader.js +73 -47
  42. package/dist/loader.js.map +1 -1
  43. package/dist/packageVersion.d.ts +1 -1
  44. package/dist/packageVersion.js +1 -1
  45. package/dist/packageVersion.js.map +1 -1
  46. package/dist/protocol.d.ts.map +1 -1
  47. package/dist/protocol.js +2 -3
  48. package/dist/protocol.js.map +1 -1
  49. package/dist/quorum.d.ts +4 -1
  50. package/dist/quorum.d.ts.map +1 -1
  51. package/dist/quorum.js +1 -13
  52. package/dist/quorum.js.map +1 -1
  53. package/dist/utils.d.ts +8 -1
  54. package/dist/utils.d.ts.map +1 -1
  55. package/dist/utils.js +24 -6
  56. package/dist/utils.js.map +1 -1
  57. package/lib/audience.d.ts +1 -0
  58. package/lib/audience.d.ts.map +1 -1
  59. package/lib/audience.js +3 -1
  60. package/lib/audience.js.map +1 -1
  61. package/lib/connectionManager.d.ts +1 -1
  62. package/lib/connectionManager.d.ts.map +1 -1
  63. package/lib/connectionManager.js +32 -35
  64. package/lib/connectionManager.js.map +1 -1
  65. package/lib/connectionStateHandler.d.ts +2 -1
  66. package/lib/connectionStateHandler.d.ts.map +1 -1
  67. package/lib/connectionStateHandler.js +9 -16
  68. package/lib/connectionStateHandler.js.map +1 -1
  69. package/lib/container.d.ts +12 -8
  70. package/lib/container.d.ts.map +1 -1
  71. package/lib/container.js +200 -157
  72. package/lib/container.js.map +1 -1
  73. package/lib/containerContext.d.ts +2 -12
  74. package/lib/containerContext.d.ts.map +1 -1
  75. package/lib/containerContext.js +1 -20
  76. package/lib/containerContext.js.map +1 -1
  77. package/lib/containerStorageAdapter.js +3 -5
  78. package/lib/containerStorageAdapter.js.map +1 -1
  79. package/lib/contracts.d.ts +11 -2
  80. package/lib/contracts.d.ts.map +1 -1
  81. package/lib/contracts.js +3 -3
  82. package/lib/contracts.js.map +1 -1
  83. package/lib/debugLogger.d.ts +30 -0
  84. package/lib/debugLogger.d.ts.map +1 -0
  85. package/lib/debugLogger.js +91 -0
  86. package/lib/debugLogger.js.map +1 -0
  87. package/lib/deltaManager.d.ts +16 -4
  88. package/lib/deltaManager.d.ts.map +1 -1
  89. package/lib/deltaManager.js +77 -28
  90. package/lib/deltaManager.js.map +1 -1
  91. package/lib/deltaQueue.js +1 -2
  92. package/lib/deltaQueue.js.map +1 -1
  93. package/lib/loader.d.ts +12 -0
  94. package/lib/loader.d.ts.map +1 -1
  95. package/lib/loader.js +73 -47
  96. package/lib/loader.js.map +1 -1
  97. package/lib/packageVersion.d.ts +1 -1
  98. package/lib/packageVersion.js +1 -1
  99. package/lib/packageVersion.js.map +1 -1
  100. package/lib/protocol.d.ts.map +1 -1
  101. package/lib/protocol.js +2 -3
  102. package/lib/protocol.js.map +1 -1
  103. package/lib/quorum.d.ts +4 -1
  104. package/lib/quorum.d.ts.map +1 -1
  105. package/lib/quorum.js +0 -11
  106. package/lib/quorum.js.map +1 -1
  107. package/lib/utils.d.ts +8 -1
  108. package/lib/utils.d.ts.map +1 -1
  109. package/lib/utils.js +22 -5
  110. package/lib/utils.js.map +1 -1
  111. package/package.json +14 -14
  112. package/src/audience.ts +6 -0
  113. package/src/connectionManager.ts +13 -14
  114. package/src/connectionStateHandler.ts +3 -2
  115. package/src/container.ts +178 -120
  116. package/src/containerContext.ts +0 -24
  117. package/src/contracts.ts +16 -5
  118. package/src/debugLogger.ts +113 -0
  119. package/src/deltaManager.ts +50 -9
  120. package/src/loader.ts +53 -30
  121. package/src/packageVersion.ts +1 -1
  122. package/src/protocol.ts +0 -1
  123. package/src/quorum.ts +0 -10
  124. package/src/utils.ts +29 -0
package/lib/container.js CHANGED
@@ -8,19 +8,19 @@ import { v4 as uuid } from "uuid";
8
8
  import { TypedEventEmitter, assert, performance, unreachableCase, } from "@fluidframework/common-utils";
9
9
  import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
10
10
  import { GenericError, UsageError } from "@fluidframework/container-utils";
11
- import { readAndParse, OnlineStatus, isOnline, combineAppAndProtocolSummary, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, canBeCoalescedByService, } from "@fluidframework/driver-utils";
11
+ import { readAndParse, OnlineStatus, isOnline, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, canBeCoalescedByService, } from "@fluidframework/driver-utils";
12
12
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
13
- import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
13
+ import { createChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, connectedEventName, normalizeError, createChildMonitoringContext, wrapError, formatTick, } from "@fluidframework/telemetry-utils";
14
14
  import { Audience } from "./audience";
15
15
  import { ContainerContext } from "./containerContext";
16
- import { ReconnectMode, getPackageName } from "./contracts";
16
+ import { ReconnectMode, getPackageName, } from "./contracts";
17
17
  import { DeltaManager } from "./deltaManager";
18
18
  import { RelativeLoader } from "./loader";
19
19
  import { pkgVersion } from "./packageVersion";
20
20
  import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTreeWithBlobContents, } from "./containerStorageAdapter";
21
21
  import { createConnectionStateHandler } from "./connectionStateHandler";
22
- import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
23
- import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
22
+ import { combineAppAndProtocolSummary, getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer, } from "./utils";
23
+ import { initQuorumValuesFromCodeDetails } from "./quorum";
24
24
  import { NoopHeuristic } from "./noopHeuristic";
25
25
  import { ConnectionManager } from "./connectionManager";
26
26
  import { ConnectionState } from "./connectionState";
@@ -104,7 +104,7 @@ export async function waitContainerToCatchUp(container) {
104
104
  }
105
105
  const getCodeProposal =
106
106
  // 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"); };
107
+ (quorum) => quorum.get("code") ?? quorum.get("code2");
108
108
  /**
109
109
  * Helper function to report to telemetry cases where operation takes longer than expected (200ms)
110
110
  * @param logger - logger to use
@@ -124,7 +124,6 @@ export class Container extends EventEmitterWithErrorHandling {
124
124
  * @internal
125
125
  */
126
126
  constructor(createProps, loadProps) {
127
- var _a;
128
127
  super((name, error) => {
129
128
  this.mc.logger.sendErrorEvent({
130
129
  eventName: "ContainerEventHandlerException",
@@ -152,7 +151,6 @@ export class Container extends EventEmitterWithErrorHandling {
152
151
  this.inboundQueuePausedFromInit = true;
153
152
  this.firstConnection = true;
154
153
  this.connectionTransitionTimes = [];
155
- this.messageCountAfterDisconnection = 0;
156
154
  this.attachStarted = false;
157
155
  this._dirtyContainer = false;
158
156
  this.savedOps = [];
@@ -175,8 +173,8 @@ export class Container extends EventEmitterWithErrorHandling {
175
173
  };
176
174
  const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
177
175
  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;
176
+ const pendingLocalState = loadProps?.pendingLocalState;
177
+ this._canReconnect = canReconnect ?? true;
180
178
  this.clientDetailsOverride = clientDetailsOverride;
181
179
  this.urlResolver = urlResolver;
182
180
  this.serviceFactory = documentServiceFactory;
@@ -184,14 +182,17 @@ export class Container extends EventEmitterWithErrorHandling {
184
182
  // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
185
183
  // all clients that were loaded from the same loader (including summarizer clients).
186
184
  // Tracking alternative ways to handle this in AB#4129.
187
- this.options = Object.assign({}, options);
185
+ this.options = { ...options };
188
186
  this.scope = scope;
189
187
  this.detachedBlobStorage = detachedBlobStorage;
190
188
  this.protocolHandlerBuilder =
191
- protocolHandlerBuilder !== null && protocolHandlerBuilder !== void 0 ? protocolHandlerBuilder : ((...args) => new ProtocolHandler(...args, new Audience()));
189
+ protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
192
190
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
193
191
  this.clone = async (_loadProps, createParamOverrides) => {
194
- return Container.load(_loadProps, Object.assign(Object.assign({}, createProps), createParamOverrides));
192
+ return Container.load(_loadProps, {
193
+ ...createProps,
194
+ ...createParamOverrides,
195
+ });
195
196
  };
196
197
  // Create logger for data stores to use
197
198
  const type = this.client.details.type;
@@ -199,42 +200,42 @@ export class Container extends EventEmitterWithErrorHandling {
199
200
  const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
200
201
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
201
202
  // 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
203
+ this.subLogger = createChildLogger({
204
+ logger: subLogger,
205
+ properties: {
206
+ all: {
207
+ clientType,
208
+ containerId: uuid(),
209
+ docId: () => this.resolvedUrl?.id,
210
+ containerAttachState: () => this._attachState,
211
+ containerLifecycleState: () => this._lifecycleState,
212
+ containerConnectionState: () => ConnectionState[this.connectionState],
213
+ serializedContainer: pendingLocalState !== undefined,
214
+ },
215
+ // we need to be judicious with our logging here to avoid generating too much data
216
+ // all data logged here should be broadly applicable, and not specific to a
217
+ // specific error or class of errors
218
+ error: {
219
+ // load information to associate errors with the specific load point
220
+ dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
221
+ dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
222
+ dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
223
+ containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
224
+ containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
225
+ // message information to associate errors with the specific execution state
226
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
227
+ dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
228
+ dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
229
+ dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
229
230
  ? "null"
230
- : (_d = (_c = this.deltaManager) === null || _c === void 0 ? void 0 : _c.lastMessage) === null || _d === void 0 ? void 0 : _d.clientId;
231
+ : this.deltaManager?.lastMessage?.clientId,
232
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
233
+ connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
231
234
  },
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
235
  },
235
236
  });
236
237
  // Prefix all events in this file with container-loader
237
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
238
+ this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
238
239
  this._deltaManager = this.createDeltaManager();
239
240
  this.connectionStateHandler = createConnectionStateHandler({
240
241
  logger: this.mc.logger,
@@ -258,9 +259,14 @@ export class Container extends EventEmitterWithErrorHandling {
258
259
  // Report issues only if we already loaded container - op processing is paused while container is loading,
259
260
  // so we always time-out processing of join op in cases where fetching snapshot takes a minute.
260
261
  // 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) })));
262
+ this._deltaManager.logConnectionIssue({
263
+ eventName,
264
+ mode,
265
+ category: this._lifecycleState === "loading" ? "generic" : category,
266
+ duration: performance.now() -
267
+ this.connectionTransitionTimes[ConnectionState.CatchingUp],
268
+ ...(details === undefined ? {} : { details: JSON.stringify(details) }),
269
+ });
264
270
  // If this is "write" connection, it took too long to receive join op. But in most cases that's due
265
271
  // to very slow op fetches and we will eventually get there.
266
272
  // For "read" connections, we get here due to self join signal not arriving on time. We will need to
@@ -277,7 +283,7 @@ export class Container extends EventEmitterWithErrorHandling {
277
283
  clientShouldHaveLeft: (clientId) => {
278
284
  this.clientsWhoShouldHaveLeft.add(clientId);
279
285
  },
280
- }, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
286
+ }, this.deltaManager, pendingLocalState?.clientId);
281
287
  this.on(savedContainerEvent, () => {
282
288
  this.connectionStateHandler.containerSaved();
283
289
  });
@@ -289,14 +295,15 @@ export class Container extends EventEmitterWithErrorHandling {
289
295
  : combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
290
296
  // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
291
297
  // 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);
298
+ const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
299
+ options.summarizeProtocolTree;
300
+ this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
294
301
  const isDomAvailable = typeof document === "object" &&
295
302
  document !== null &&
296
303
  typeof document.addEventListener === "function" &&
297
304
  document.addEventListener !== null;
298
- // keep track of last time page was visible for telemetry
299
- if (isDomAvailable) {
305
+ // keep track of last time page was visible for telemetry (on interactive clients only)
306
+ if (isDomAvailable && interactive) {
300
307
  this.lastVisible = document.hidden ? performance.now() : undefined;
301
308
  this.visibilityEventHandler = () => {
302
309
  if (document.hidden) {
@@ -317,7 +324,7 @@ export class Container extends EventEmitterWithErrorHandling {
317
324
  * @internal
318
325
  */
319
326
  static async load(loadProps, createProps) {
320
- const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
327
+ const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } = loadProps;
321
328
  const container = new Container(createProps, loadProps);
322
329
  const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
323
330
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
@@ -325,19 +332,20 @@ export class Container extends EventEmitterWithErrorHandling {
325
332
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
326
333
  // to return container, so ignore this value and use undefined for opsBeforeReturn
327
334
  const mode = pendingLocalState
328
- ? Object.assign(Object.assign({}, (loadMode !== null && loadMode !== void 0 ? loadMode : defaultMode)), { opsBeforeReturn: undefined }) : loadMode !== null && loadMode !== void 0 ? loadMode : defaultMode;
335
+ ? { ...(loadMode ?? defaultMode), opsBeforeReturn: undefined }
336
+ : loadMode ?? defaultMode;
329
337
  const onClosed = (err) => {
330
338
  // pre-0.58 error message: containerClosedWithoutErrorDuringLoad
331
- reject(err !== null && err !== void 0 ? err : new GenericError("Container closed without error during load"));
339
+ reject(err ?? new GenericError("Container closed without error during load"));
332
340
  };
333
341
  container.on("closed", onClosed);
334
342
  container
335
- .load(version, mode, resolvedUrl, pendingLocalState)
343
+ .load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
336
344
  .finally(() => {
337
345
  container.removeListener("closed", onClosed);
338
346
  })
339
347
  .then((props) => {
340
- event.end(Object.assign(Object.assign({}, props), loadMode));
348
+ event.end({ ...props, ...loadMode });
341
349
  resolve(container);
342
350
  }, (error) => {
343
351
  const err = normalizeError(error);
@@ -381,10 +389,10 @@ export class Container extends EventEmitterWithErrorHandling {
381
389
  }
382
390
  }
383
391
  get closed() {
384
- return (this._lifecycleState === "closing" ||
385
- this._lifecycleState === "closed" ||
386
- this._lifecycleState === "disposing" ||
387
- this._lifecycleState === "disposed");
392
+ return (this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed);
393
+ }
394
+ get disposed() {
395
+ return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
388
396
  }
389
397
  get runtime() {
390
398
  if (this._runtime === undefined) {
@@ -405,7 +413,6 @@ export class Container extends EventEmitterWithErrorHandling {
405
413
  return this;
406
414
  }
407
415
  get resolvedUrl() {
408
- var _a;
409
416
  /**
410
417
  * All attached containers will have a document service,
411
418
  * this is required, as attached containers are attached to
@@ -417,7 +424,7 @@ export class Container extends EventEmitterWithErrorHandling {
417
424
  * is always the same as the containers, as we had to
418
425
  * obtain the resolved url, and then create the service from it.
419
426
  */
420
- return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
427
+ return this.service?.resolvedUrl;
421
428
  }
422
429
  get readOnlyInfo() {
423
430
  return this._deltaManager.readOnlyInfo;
@@ -445,8 +452,8 @@ export class Container extends EventEmitterWithErrorHandling {
445
452
  return this._clientId;
446
453
  }
447
454
  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;
455
+ const enabled = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
456
+ this.options?.enableOfflineLoad === true;
450
457
  // summarizer will not have any pending state we want to save
451
458
  return enabled && this.deltaManager.clientDetails.capabilities.interactive;
452
459
  }
@@ -483,18 +490,16 @@ export class Container extends EventEmitterWithErrorHandling {
483
490
  * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
484
491
  */
485
492
  async getEntryPoint() {
486
- var _a, _b;
487
493
  if (this._disposed) {
488
494
  throw new UsageError("The context is already disposed");
489
495
  }
490
496
  if (this._runtime !== undefined) {
491
- return (_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a);
497
+ return this._runtime.getEntryPoint?.();
492
498
  }
493
499
  return new Promise((resolve, reject) => {
494
500
  const runtimeInstantiatedHandler = () => {
495
- var _a, _b;
496
501
  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));
502
+ resolve(this._runtime.getEntryPoint?.());
498
503
  this._lifecycleEvents.off("disposed", disposedHandler);
499
504
  };
500
505
  const disposedHandler = () => {
@@ -528,7 +533,6 @@ export class Container extends EventEmitterWithErrorHandling {
528
533
  assert(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
529
534
  }
530
535
  closeCore(error) {
531
- var _a;
532
536
  assert(!this.closed, 0x315 /* re-entrancy */);
533
537
  try {
534
538
  // Ensure that we raise all key events even if one of these throws
@@ -544,7 +548,7 @@ export class Container extends EventEmitterWithErrorHandling {
544
548
  : "generic",
545
549
  }, error);
546
550
  this._lifecycleState = "closing";
547
- (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
551
+ this._protocolHandler?.close();
548
552
  this.connectionStateHandler.dispose();
549
553
  }
550
554
  catch (exception) {
@@ -564,7 +568,6 @@ export class Container extends EventEmitterWithErrorHandling {
564
568
  }
565
569
  }
566
570
  disposeCore(error) {
567
- var _a, _b, _c;
568
571
  assert(!this._disposed, 0x54c /* Container already disposed */);
569
572
  this._disposed = true;
570
573
  try {
@@ -581,15 +584,15 @@ export class Container extends EventEmitterWithErrorHandling {
581
584
  if (this._lifecycleState !== "closed") {
582
585
  this._lifecycleState = "disposing";
583
586
  }
584
- (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
587
+ this._protocolHandler?.close();
585
588
  this.connectionStateHandler.dispose();
586
589
  const maybeError = error !== undefined ? new Error(error.message) : undefined;
587
- (_b = this._runtime) === null || _b === void 0 ? void 0 : _b.dispose(maybeError);
590
+ this._runtime?.dispose(maybeError);
588
591
  this.storageAdapter.dispose();
589
592
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
590
593
  // about file, like file being overwritten in storage, but client having stale local cache.
591
594
  // Driver need to ensure all caches are cleared on critical errors
592
- (_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
595
+ this.service?.dispose(error);
593
596
  }
594
597
  catch (exception) {
595
598
  this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
@@ -605,15 +608,19 @@ export class Container extends EventEmitterWithErrorHandling {
605
608
  this._lifecycleEvents.emit("disposed");
606
609
  }
607
610
  }
608
- closeAndGetPendingLocalState() {
611
+ async closeAndGetPendingLocalState() {
609
612
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
610
613
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
611
614
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
612
- const pendingState = this.getPendingLocalState();
615
+ this.disconnect(); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
616
+ const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
613
617
  this.close();
614
618
  return pendingState;
615
619
  }
616
- getPendingLocalState() {
620
+ async getPendingLocalState() {
621
+ return this.getPendingLocalStateCore({ notifyImminentClosure: false });
622
+ }
623
+ async getPendingLocalStateCore(props) {
617
624
  if (!this.offlineLoadEnabled) {
618
625
  throw new UsageError("Can't get pending local state unless offline load is enabled");
619
626
  }
@@ -624,8 +631,9 @@ export class Container extends EventEmitterWithErrorHandling {
624
631
  assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
625
632
  assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
626
633
  assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
634
+ const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
627
635
  const pendingState = {
628
- pendingRuntimeState: this.runtime.getPendingLocalState(),
636
+ pendingRuntimeState,
629
637
  baseSnapshot: this.baseSnapshot,
630
638
  snapshotBlobs: this.baseSnapshotBlobs,
631
639
  savedOps: this.savedOps,
@@ -654,7 +662,6 @@ export class Container extends EventEmitterWithErrorHandling {
654
662
  }
655
663
  async attach(request) {
656
664
  await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
657
- var _a;
658
665
  if (this._lifecycleState !== "loaded") {
659
666
  // pre-0.58 error message: containerNotValidForAttach
660
667
  throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
@@ -746,7 +753,7 @@ export class Container extends EventEmitterWithErrorHandling {
746
753
  catch (error) {
747
754
  // add resolved URL on error object so that host has the ability to find this document and delete it
748
755
  const newError = normalizeError(error);
749
- newError.addTelemetryProperties({ resolvedUrl: (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.url });
756
+ newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
750
757
  this.close(newError);
751
758
  throw newError;
752
759
  }
@@ -853,7 +860,6 @@ export class Container extends EventEmitterWithErrorHandling {
853
860
  * Determines if the currently loaded module satisfies the incoming constraint code details
854
861
  */
855
862
  async satisfies(constraintCodeDetails) {
856
- var _a, _b;
857
863
  // If we have no module, it can't satisfy anything.
858
864
  if (this._loadedModule === undefined) {
859
865
  return false;
@@ -863,8 +869,8 @@ export class Container extends EventEmitterWithErrorHandling {
863
869
  if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
864
870
  comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
865
871
  }
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) {
872
+ const maybeCompareExport = this._loadedModule?.module.fluidExport;
873
+ if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
868
874
  comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
869
875
  }
870
876
  // If there are no comparers, then it's impossible to know if the currently loaded package satisfies
@@ -874,7 +880,7 @@ export class Container extends EventEmitterWithErrorHandling {
874
880
  return false;
875
881
  }
876
882
  for (const comparer of comparers) {
877
- const satisfies = await comparer.satisfies((_b = this._loadedModule) === null || _b === void 0 ? void 0 : _b.details, constraintCodeDetails);
883
+ const satisfies = await comparer.satisfies(this._loadedModule?.details, constraintCodeDetails);
878
884
  if (satisfies === false) {
879
885
  return false;
880
886
  }
@@ -897,8 +903,7 @@ export class Container extends EventEmitterWithErrorHandling {
897
903
  *
898
904
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
899
905
  */
900
- async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
901
- var _a, _b, _c;
906
+ async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
902
907
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
903
908
  // Ideally we always connect as "read" by default.
904
909
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
@@ -947,9 +952,51 @@ export class Container extends EventEmitterWithErrorHandling {
947
952
  }
948
953
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
949
954
  // 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;
955
+ const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
956
+ const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
952
957
  let opsBeforeReturnP;
958
+ if (loadMode.pauseAfterLoad === true) {
959
+ // If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
960
+ if (loadMode.opsBeforeReturn === "sequenceNumber") {
961
+ assert(loadToSequenceNumber !== undefined, 0x727 /* sequenceNumber should be defined */);
962
+ // Note: It is possible that we think the latest snapshot is newer than the specified sequence number
963
+ // due to saved ops that may be replayed after the snapshot.
964
+ // https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
965
+ if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
966
+ 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.");
967
+ }
968
+ }
969
+ // Force readonly mode - this will ensure we don't receive an error for the lack of join op
970
+ this.forceReadonly(true);
971
+ // We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
972
+ const opHandler = () => {
973
+ if (loadToSequenceNumber === undefined) {
974
+ // If there is no specified sequence number, pause after the inbound queue is empty.
975
+ if (this.deltaManager.inbound.length !== 0) {
976
+ return;
977
+ }
978
+ }
979
+ else {
980
+ // If there is a specified sequence number, keep processing until we reach it.
981
+ if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
982
+ return;
983
+ }
984
+ }
985
+ // Pause op processing once we have processed the desired number of ops.
986
+ void this.deltaManager.inbound.pause();
987
+ void this.deltaManager.outbound.pause();
988
+ this.off("op", opHandler);
989
+ };
990
+ if ((loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
991
+ this.deltaManager.lastSequenceNumber === loadToSequenceNumber) {
992
+ // If we have already reached the desired sequence number, call opHandler() to pause immediately.
993
+ opHandler();
994
+ }
995
+ else {
996
+ // If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
997
+ this.on("op", opHandler);
998
+ }
999
+ }
953
1000
  // Attach op handlers to finish initialization and be able to start processing ops
954
1001
  // Kick off any ops fetching if required.
955
1002
  switch (loadMode.opsBeforeReturn) {
@@ -958,6 +1005,9 @@ export class Container extends EventEmitterWithErrorHandling {
958
1005
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
959
1006
  this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
960
1007
  break;
1008
+ case "sequenceNumber":
1009
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "sequenceNumber");
1010
+ break;
961
1011
  case "cached":
962
1012
  opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
963
1013
  break;
@@ -971,19 +1021,18 @@ export class Container extends EventEmitterWithErrorHandling {
971
1021
  // Initialize the protocol handler
972
1022
  await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
973
1023
  const codeDetails = this.getCodeDetailsFromQuorum();
974
- await this.instantiateContext(true, // existing
975
- codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
1024
+ await this.instantiateRuntime(codeDetails, snapshot, pendingLocalState?.pendingRuntimeState);
976
1025
  // replay saved ops
977
1026
  if (pendingLocalState) {
978
1027
  for (const message of pendingLocalState.savedOps) {
979
1028
  this.processRemoteMessage(message);
980
1029
  // 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));
1030
+ await this.runtime.notifyOpReplay?.(message);
982
1031
  }
983
1032
  pendingLocalState.savedOps = [];
984
1033
  // now set clientId to stashed clientId so live ops are correctly processed as local
985
1034
  assert(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
986
- this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
1035
+ this._clientId = pendingLocalState?.clientId;
987
1036
  }
988
1037
  // We might have hit some failure that did not manifest itself in exception in this flow,
989
1038
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
@@ -1014,6 +1063,19 @@ export class Container extends EventEmitterWithErrorHandling {
1014
1063
  unreachableCase(loadMode.deltaConnection);
1015
1064
  }
1016
1065
  }
1066
+ // If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
1067
+ if (loadToSequenceNumber !== undefined &&
1068
+ this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
1069
+ await new Promise((resolve, reject) => {
1070
+ const opHandler = (message) => {
1071
+ if (message.sequenceNumber >= loadToSequenceNumber) {
1072
+ resolve();
1073
+ this.off("op", opHandler);
1074
+ }
1075
+ };
1076
+ this.on("op", opHandler);
1077
+ });
1078
+ }
1017
1079
  // Safety net: static version of Container.load() should have learned about it through "closed" handler.
1018
1080
  // But if that did not happen for some reason, fail load for sure.
1019
1081
  // Otherwise we can get into situations where container is closed and does not try to connect to ordering
@@ -1031,7 +1093,7 @@ export class Container extends EventEmitterWithErrorHandling {
1031
1093
  dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
1032
1094
  };
1033
1095
  }
1034
- async createDetached(source) {
1096
+ async createDetached(codeDetails) {
1035
1097
  const attributes = {
1036
1098
  sequenceNumber: detachedContainerRefSeqNumber,
1037
1099
  term: OnlyValidTermValue,
@@ -1039,14 +1101,13 @@ export class Container extends EventEmitterWithErrorHandling {
1039
1101
  };
1040
1102
  await this.attachDeltaManagerOpHandler(attributes);
1041
1103
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1042
- const qValues = initQuorumValuesFromCodeDetails(source);
1104
+ const qValues = initQuorumValuesFromCodeDetails(codeDetails);
1043
1105
  this.initializeProtocolState(attributes, {
1044
1106
  members: [],
1045
1107
  proposals: [],
1046
1108
  values: qValues,
1047
1109
  });
1048
- // The load context - given we seeded the quorum - will be great
1049
- await this.instantiateContextDetached(false);
1110
+ await this.instantiateRuntime(codeDetails, undefined);
1050
1111
  this.setLoaded();
1051
1112
  }
1052
1113
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
@@ -1061,14 +1122,13 @@ export class Container extends EventEmitterWithErrorHandling {
1061
1122
  // Initialize the protocol handler
1062
1123
  const baseTree = getProtocolSnapshotTree(snapshotTree);
1063
1124
  const qValues = await readAndParse(this.storageAdapter, baseTree.blobs.quorumValues);
1064
- const codeDetails = getCodeDetailsFromQuorumValues(qValues);
1065
1125
  this.initializeProtocolState(attributes, {
1066
1126
  members: [],
1067
1127
  proposals: [],
1068
- values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
1128
+ values: qValues,
1069
1129
  });
1070
- await this.instantiateContextDetached(true, // existing
1071
- snapshotTree);
1130
+ const codeDetails = this.getCodeDetailsFromQuorum();
1131
+ await this.instantiateRuntime(codeDetails, snapshotTree);
1072
1132
  this.setLoaded();
1073
1133
  }
1074
1134
  async getDocumentAttributes(storage, tree) {
@@ -1105,7 +1165,10 @@ export class Container extends EventEmitterWithErrorHandling {
1105
1165
  }
1106
1166
  initializeProtocolState(attributes, quorumSnapshot) {
1107
1167
  const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })));
1108
- const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
1168
+ const protocolLogger = createChildLogger({
1169
+ logger: this.subLogger,
1170
+ namespace: "ProtocolHandler",
1171
+ });
1109
1172
  protocol.quorum.on("error", (error) => {
1110
1173
  protocolLogger.sendErrorEvent(error);
1111
1174
  });
@@ -1167,8 +1230,7 @@ export class Container extends EventEmitterWithErrorHandling {
1167
1230
  return pkg;
1168
1231
  }
1169
1232
  get client() {
1170
- var _a;
1171
- const client = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.client) !== undefined
1233
+ const client = this.options?.client !== undefined
1172
1234
  ? this.options.client
1173
1235
  : {
1174
1236
  details: {
@@ -1199,7 +1261,7 @@ export class Container extends EventEmitterWithErrorHandling {
1199
1261
  }
1200
1262
  createDeltaManager() {
1201
1263
  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));
1264
+ 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
1265
  // Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
1204
1266
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1205
1267
  deltaManager.inbound.pause();
@@ -1216,8 +1278,7 @@ export class Container extends EventEmitterWithErrorHandling {
1216
1278
  this.connectionStateHandler.cancelEstablishingConnection(reason);
1217
1279
  });
1218
1280
  deltaManager.on("disconnect", (reason, error) => {
1219
- var _a;
1220
- (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
1281
+ this.noopHeuristic?.notifyDisconnect();
1221
1282
  if (!this.closed) {
1222
1283
  this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1223
1284
  }
@@ -1252,7 +1313,6 @@ export class Container extends EventEmitterWithErrorHandling {
1252
1313
  }, prefetchType);
1253
1314
  }
1254
1315
  logConnectionStateChangeTelemetry(value, oldState, reason, error) {
1255
- var _a;
1256
1316
  // Log actual event
1257
1317
  const time = performance.now();
1258
1318
  this.connectionTransitionTimes[value] = time;
@@ -1269,7 +1329,7 @@ export class Container extends EventEmitterWithErrorHandling {
1269
1329
  if (value === ConnectionState.Connected) {
1270
1330
  durationFromDisconnected =
1271
1331
  time - this.connectionTransitionTimes[ConnectionState.Disconnected];
1272
- durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
1332
+ durationFromDisconnected = formatTick(durationFromDisconnected);
1273
1333
  }
1274
1334
  else if (value === ConnectionState.CatchingUp) {
1275
1335
  // This info is of most interesting while Catching Up.
@@ -1282,19 +1342,31 @@ export class Container extends EventEmitterWithErrorHandling {
1282
1342
  }
1283
1343
  connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1284
1344
  }
1285
- this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${ConnectionState[value]}`, from: ConnectionState[oldState], duration,
1345
+ this.mc.logger.sendPerformanceEvent({
1346
+ eventName: `ConnectionStateChange_${ConnectionState[value]}`,
1347
+ from: ConnectionState[oldState],
1348
+ duration,
1286
1349
  durationFromDisconnected,
1287
1350
  reason,
1288
- connectionInitiationReason, pendingClientId: this.connectionStateHandler.pendingClientId, clientId: this.clientId, autoReconnect,
1289
- opsBehind, online: OnlineStatus[isOnline()], lastVisible: this.lastVisible !== undefined
1351
+ connectionInitiationReason,
1352
+ pendingClientId: this.connectionStateHandler.pendingClientId,
1353
+ clientId: this.clientId,
1354
+ autoReconnect,
1355
+ opsBehind,
1356
+ online: OnlineStatus[isOnline()],
1357
+ lastVisible: this.lastVisible !== undefined
1290
1358
  ? 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);
1359
+ : undefined,
1360
+ checkpointSequenceNumber,
1361
+ quorumSize: this._protocolHandler?.quorum.getMembers().size,
1362
+ isDirty: this.isDirty,
1363
+ ...this._deltaManager.connectionProps,
1364
+ }, error);
1292
1365
  if (value === ConnectionState.Connected) {
1293
1366
  this.firstConnection = false;
1294
1367
  }
1295
1368
  }
1296
1369
  propagateConnectionState(initialTransition, disconnectedReason) {
1297
- var _a;
1298
1370
  // When container loaded, we want to propagate initial connection state.
1299
1371
  // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1300
1372
  // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
@@ -1304,22 +1376,10 @@ export class Container extends EventEmitterWithErrorHandling {
1304
1376
  return;
1305
1377
  }
1306
1378
  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
1379
  // 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);
1380
+ this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
1315
1381
  this.protocolHandler.setConnectionState(state, this.clientId);
1316
1382
  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
- }
1323
1383
  }
1324
1384
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1325
1385
  submitContainerMessage(type, contents, batch, metadata) {
@@ -1357,13 +1417,11 @@ export class Container extends EventEmitterWithErrorHandling {
1357
1417
  return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
1358
1418
  }
1359
1419
  submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
1360
- var _a;
1361
1420
  if (this.connectionState !== ConnectionState.Connected) {
1362
1421
  this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
1363
1422
  return -1;
1364
1423
  }
1365
- this.messageCountAfterDisconnection += 1;
1366
- (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
1424
+ this.noopHeuristic?.notifyMessageSent();
1367
1425
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1368
1426
  }
1369
1427
  processRemoteMessage(message) {
@@ -1438,8 +1496,7 @@ export class Container extends EventEmitterWithErrorHandling {
1438
1496
  * @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
1439
1497
  */
1440
1498
  async fetchSnapshotTree(specifiedVersion) {
1441
- var _a;
1442
- const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
1499
+ const version = await this.getVersion(specifiedVersion ?? null);
1443
1500
  if (version === undefined && specifiedVersion !== undefined) {
1444
1501
  // We should have a defined version to load from if specified version requested
1445
1502
  this.mc.logger.sendErrorEvent({
@@ -1448,22 +1505,14 @@ export class Container extends EventEmitterWithErrorHandling {
1448
1505
  });
1449
1506
  }
1450
1507
  this._loadedFromVersion = version;
1451
- const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
1508
+ const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
1452
1509
  if (snapshot === undefined && version !== undefined) {
1453
1510
  this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
1454
1511
  }
1455
- return { snapshot, versionId: version === null || version === void 0 ? void 0 : version.id };
1456
- }
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);
1512
+ return { snapshot, versionId: version?.id };
1463
1513
  }
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" */);
1514
+ async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
1515
+ assert(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
1467
1516
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1468
1517
  // are set. Global requests will still go directly to the loader
1469
1518
  const maybeLoader = this.scope;
@@ -1474,25 +1523,20 @@ export class Container extends EventEmitterWithErrorHandling {
1474
1523
  // An older interface ICodeLoader could return an IFluidModule which didn't have details.
1475
1524
  // If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
1476
1525
  // 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,
1526
+ details: loadCodeResult.details ?? codeDetails,
1478
1527
  };
1479
1528
  const fluidExport = this._loadedModule.module.fluidExport;
1480
- const runtimeFactory = fluidExport === null || fluidExport === void 0 ? void 0 : fluidExport.IRuntimeFactory;
1529
+ const runtimeFactory = fluidExport?.IRuntimeFactory;
1481
1530
  if (runtimeFactory === undefined) {
1482
1531
  throw new Error(packageNotFactoryError);
1483
1532
  }
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
- });
1533
+ const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
1534
+ this.protocolHandler.quorum.get("code2"));
1535
+ const existing = snapshot !== undefined;
1536
+ 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.clientId, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
1492
1537
  this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1493
1538
  this._lifecycleEvents.emit("runtimeInstantiated");
1494
1539
  this._loadedCodeDetails = codeDetails;
1495
- this.emit("contextChanged", codeDetails);
1496
1540
  }
1497
1541
  /**
1498
1542
  * Set the connected state of the ContainerContext
@@ -1501,8 +1545,7 @@ export class Container extends EventEmitterWithErrorHandling {
1501
1545
  * @param readonly - Is the container in readonly mode?
1502
1546
  */
1503
1547
  setContextConnectedState(state, readonly) {
1504
- var _a;
1505
- if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1548
+ if (this._runtime?.disposed === false) {
1506
1549
  /**
1507
1550
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1508
1551
  * ops getting through to the DeltaManager.