@fluidframework/container-loader 0.59.4001 → 1.1.0-75972

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 (135) hide show
  1. package/.eslintrc.js +1 -1
  2. package/README.md +1 -1
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +3 -1
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionState.d.ts +15 -3
  7. package/dist/connectionState.d.ts.map +1 -1
  8. package/dist/connectionState.js +15 -3
  9. package/dist/connectionState.js.map +1 -1
  10. package/dist/connectionStateHandler.d.ts +47 -11
  11. package/dist/connectionStateHandler.d.ts.map +1 -1
  12. package/dist/connectionStateHandler.js +108 -38
  13. package/dist/connectionStateHandler.js.map +1 -1
  14. package/dist/container.d.ts +20 -28
  15. package/dist/container.d.ts.map +1 -1
  16. package/dist/container.js +97 -153
  17. package/dist/container.js.map +1 -1
  18. package/dist/containerContext.d.ts +6 -4
  19. package/dist/containerContext.d.ts.map +1 -1
  20. package/dist/containerContext.js +8 -7
  21. package/dist/containerContext.js.map +1 -1
  22. package/dist/containerStorageAdapter.d.ts +4 -5
  23. package/dist/containerStorageAdapter.d.ts.map +1 -1
  24. package/dist/containerStorageAdapter.js +4 -7
  25. package/dist/containerStorageAdapter.js.map +1 -1
  26. package/dist/contracts.d.ts +1 -1
  27. package/dist/contracts.js +1 -1
  28. package/dist/contracts.js.map +1 -1
  29. package/dist/deltaManager.d.ts.map +1 -1
  30. package/dist/deltaManager.js +9 -1
  31. package/dist/deltaManager.js.map +1 -1
  32. package/dist/deltaManagerProxy.d.ts +0 -1
  33. package/dist/deltaManagerProxy.d.ts.map +1 -1
  34. package/dist/deltaManagerProxy.js +0 -3
  35. package/dist/deltaManagerProxy.js.map +1 -1
  36. package/dist/deltaQueue.d.ts.map +1 -1
  37. package/dist/deltaQueue.js +8 -3
  38. package/dist/deltaQueue.js.map +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/loader.d.ts +1 -13
  43. package/dist/loader.d.ts.map +1 -1
  44. package/dist/loader.js +2 -3
  45. package/dist/loader.js.map +1 -1
  46. package/dist/packageVersion.d.ts +1 -1
  47. package/dist/packageVersion.d.ts.map +1 -1
  48. package/dist/packageVersion.js +1 -1
  49. package/dist/packageVersion.js.map +1 -1
  50. package/dist/protocolTreeDocumentStorageService.d.ts +2 -3
  51. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  52. package/dist/protocolTreeDocumentStorageService.js +0 -1
  53. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  54. package/dist/retriableDocumentStorageService.d.ts +3 -4
  55. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  56. package/dist/retriableDocumentStorageService.js +4 -7
  57. package/dist/retriableDocumentStorageService.js.map +1 -1
  58. package/dist/utils.d.ts.map +1 -1
  59. package/dist/utils.js +3 -2
  60. package/dist/utils.js.map +1 -1
  61. package/lib/connectionManager.d.ts.map +1 -1
  62. package/lib/connectionManager.js +4 -2
  63. package/lib/connectionManager.js.map +1 -1
  64. package/lib/connectionState.d.ts +15 -3
  65. package/lib/connectionState.d.ts.map +1 -1
  66. package/lib/connectionState.js +15 -3
  67. package/lib/connectionState.js.map +1 -1
  68. package/lib/connectionStateHandler.d.ts +47 -11
  69. package/lib/connectionStateHandler.d.ts.map +1 -1
  70. package/lib/connectionStateHandler.js +108 -38
  71. package/lib/connectionStateHandler.js.map +1 -1
  72. package/lib/container.d.ts +20 -28
  73. package/lib/container.d.ts.map +1 -1
  74. package/lib/container.js +98 -154
  75. package/lib/container.js.map +1 -1
  76. package/lib/containerContext.d.ts +6 -4
  77. package/lib/containerContext.d.ts.map +1 -1
  78. package/lib/containerContext.js +8 -7
  79. package/lib/containerContext.js.map +1 -1
  80. package/lib/containerStorageAdapter.d.ts +4 -5
  81. package/lib/containerStorageAdapter.d.ts.map +1 -1
  82. package/lib/containerStorageAdapter.js +4 -7
  83. package/lib/containerStorageAdapter.js.map +1 -1
  84. package/lib/contracts.d.ts +1 -1
  85. package/lib/contracts.js +1 -1
  86. package/lib/contracts.js.map +1 -1
  87. package/lib/deltaManager.d.ts.map +1 -1
  88. package/lib/deltaManager.js +9 -1
  89. package/lib/deltaManager.js.map +1 -1
  90. package/lib/deltaManagerProxy.d.ts +0 -1
  91. package/lib/deltaManagerProxy.d.ts.map +1 -1
  92. package/lib/deltaManagerProxy.js +0 -3
  93. package/lib/deltaManagerProxy.js.map +1 -1
  94. package/lib/deltaQueue.d.ts.map +1 -1
  95. package/lib/deltaQueue.js +8 -3
  96. package/lib/deltaQueue.js.map +1 -1
  97. package/lib/index.d.ts +1 -1
  98. package/lib/index.d.ts.map +1 -1
  99. package/lib/index.js.map +1 -1
  100. package/lib/loader.d.ts +1 -13
  101. package/lib/loader.d.ts.map +1 -1
  102. package/lib/loader.js +2 -3
  103. package/lib/loader.js.map +1 -1
  104. package/lib/packageVersion.d.ts +1 -1
  105. package/lib/packageVersion.d.ts.map +1 -1
  106. package/lib/packageVersion.js +1 -1
  107. package/lib/packageVersion.js.map +1 -1
  108. package/lib/protocolTreeDocumentStorageService.d.ts +2 -3
  109. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  110. package/lib/protocolTreeDocumentStorageService.js +0 -1
  111. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  112. package/lib/retriableDocumentStorageService.d.ts +3 -4
  113. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  114. package/lib/retriableDocumentStorageService.js +4 -7
  115. package/lib/retriableDocumentStorageService.js.map +1 -1
  116. package/lib/utils.d.ts.map +1 -1
  117. package/lib/utils.js +3 -2
  118. package/lib/utils.js.map +1 -1
  119. package/package.json +14 -27
  120. package/src/connectionManager.ts +4 -6
  121. package/src/connectionState.ts +20 -6
  122. package/src/connectionStateHandler.ts +136 -56
  123. package/src/container.ts +139 -185
  124. package/src/containerContext.ts +10 -10
  125. package/src/containerStorageAdapter.ts +5 -10
  126. package/src/contracts.ts +1 -1
  127. package/src/deltaManager.ts +8 -2
  128. package/src/deltaManagerProxy.ts +0 -4
  129. package/src/deltaQueue.ts +7 -3
  130. package/src/index.ts +1 -0
  131. package/src/loader.ts +4 -21
  132. package/src/packageVersion.ts +1 -1
  133. package/src/protocolTreeDocumentStorageService.ts +0 -1
  134. package/src/retriableDocumentStorageService.ts +4 -12
  135. package/src/utils.ts +3 -2
package/dist/container.js CHANGED
@@ -42,7 +42,7 @@ const savedContainerEvent = "saved";
42
42
  * Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
43
43
  * up to date. Host may chose to wait in such case and retry resolving URI.
44
44
  * Warning: Will wait infinitely for connection to establish if there is no connection.
45
- * May result in deadlock if Container.setAutoReconnect(false) is called and never switched back to auto-reconnect.
45
+ * May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
46
46
  * @returns true: container is up to date, it processed all the ops that were know at the time of first connection
47
47
  * false: storage does not provide indication of how far the client is. Container processed
48
48
  * all the ops known to it, but it maybe still behind.
@@ -64,7 +64,8 @@ async function waitContainerToCatchUp(container) {
64
64
  };
65
65
  container.on("closed", closedCallback);
66
66
  const waitForOps = () => {
67
- (0, common_utils_1.assert)(container.connectionState !== connectionState_1.ConnectionState.Disconnected, 0x0cd /* "Container disconnected while waiting for ops!" */);
67
+ (0, common_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp
68
+ || container.connectionState === connectionState_1.ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
68
69
  const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
69
70
  const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
70
71
  (0, common_utils_1.assert)(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
@@ -95,9 +96,7 @@ async function waitContainerToCatchUp(container) {
95
96
  waitForOps();
96
97
  };
97
98
  container.on(telemetry_utils_1.connectedEventName, callback);
98
- // TODO: Remove null check after next release #8523
99
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
100
- container.resume();
99
+ container.connect();
101
100
  });
102
101
  }
103
102
  exports.waitContainerToCatchUp = waitContainerToCatchUp;
@@ -107,7 +106,7 @@ const getCodeProposal =
107
106
  const summarizerClientType = "summarizer";
108
107
  class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
109
108
  constructor(loader, config) {
110
- var _a;
109
+ var _a, _b;
111
110
  super((name, error) => {
112
111
  this.mc.logger.sendErrorEvent({
113
112
  eventName: "ContainerEventHandlerException",
@@ -118,7 +117,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
118
117
  // Tells if container can reconnect on losing fist connection
119
118
  // If false, container gets closed on loss of connection.
120
119
  this._canReconnect = true;
121
- this._lifecycleState = "created";
120
+ this._lifecycleState = "loading";
122
121
  this._attachState = container_definitions_1.AttachState.Detached;
123
122
  this.resumedOpProcessingAfterLoad = false;
124
123
  this.firstConnection = true;
@@ -174,20 +173,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
174
173
  logConnectionStateChangeTelemetry: (value, oldState, reason) => this.logConnectionStateChangeTelemetry(value, oldState, reason),
175
174
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
176
175
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
177
- logConnectionIssue: (eventName) => {
176
+ logConnectionIssue: (eventName, details) => {
178
177
  // We get here when socket does not receive any ops on "write" connection, including
179
178
  // its own join op. Attempt recovery option.
180
- this._deltaManager.logConnectionIssue({
181
- eventName,
182
- duration: common_utils_1.performance.now() - this.connectionTransitionTimes[connectionState_1.ConnectionState.Connecting],
183
- });
179
+ this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: common_utils_1.performance.now() - this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
184
180
  },
185
181
  connectionStateChanged: () => {
186
- if (this.loaded) {
182
+ // Fire events only if container is fully loaded and not closed
183
+ if (this._lifecycleState === "loaded") {
187
184
  this.propagateConnectionState();
188
185
  }
189
186
  },
190
- }, this.mc.logger);
187
+ }, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
191
188
  this.on(savedContainerEvent, () => {
192
189
  this.connectionStateHandler.containerSaved();
193
190
  });
@@ -265,16 +262,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
265
262
  clientDetailsOverride: loadOptions.clientDetailsOverride,
266
263
  resolvedUrl: loadOptions.resolvedUrl,
267
264
  canReconnect: loadOptions.canReconnect,
265
+ serializedContainerState: pendingLocalState,
268
266
  });
269
267
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
270
- var _a;
271
- container._lifecycleState = "loading";
268
+ var _a, _b;
272
269
  const version = loadOptions.version;
273
- // always load unpaused with pending ops!
274
- // It is also default mode in general.
275
270
  const defaultMode = { opsBeforeReturn: "cached" };
276
- (0, common_utils_1.assert)(pendingLocalState === undefined || loadOptions.loadMode === undefined, 0x1e1 /* "pending state requires immediate connection!" */);
277
- const mode = (_a = loadOptions.loadMode) !== null && _a !== void 0 ? _a : defaultMode;
271
+ // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
272
+ // to return container, so ignore this value and use undefined for opsBeforeReturn
273
+ const mode = pendingLocalState
274
+ ? Object.assign(Object.assign({}, ((_a = loadOptions.loadMode) !== null && _a !== void 0 ? _a : defaultMode)), { opsBeforeReturn: undefined }) : (_b = loadOptions.loadMode) !== null && _b !== void 0 ? _b : defaultMode;
278
275
  const onClosed = (err) => {
279
276
  // pre-0.58 error message: containerClosedWithoutErrorDuringLoad
280
277
  reject(err !== null && err !== void 0 ? err : new container_utils_1.GenericError("Container closed without error during load"));
@@ -303,7 +300,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
303
300
  static async createDetached(loader, codeDetails) {
304
301
  const container = new Container(loader, {});
305
302
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
306
- container._lifecycleState = "loading";
307
303
  await container.createDetached(codeDetails);
308
304
  return container;
309
305
  }, { start: true, end: true, cancel: "generic" });
@@ -316,20 +312,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
316
312
  const container = new Container(loader, {});
317
313
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
318
314
  const deserializedSummary = JSON.parse(snapshot);
319
- container._lifecycleState = "loading";
320
315
  await container.rehydrateDetachedFromSnapshot(deserializedSummary);
321
316
  return container;
322
317
  }, { start: true, end: true, cancel: "generic" });
323
318
  }
324
- get loaded() {
325
- return (this._lifecycleState !== "created" && this._lifecycleState !== "loading");
326
- }
327
- set loaded(t) {
328
- (0, common_utils_1.assert)(t, 0x27d /* "Setting loaded state to false is not supported" */);
329
- (0, common_utils_1.assert)(this._lifecycleState !== "created", 0x27e /* "Must go through loading state before loaded" */);
319
+ setLoaded() {
330
320
  // It's conceivable the container could be closed when this is called
331
321
  // Only transition states if currently loading
332
322
  if (this._lifecycleState === "loading") {
323
+ // Propagate current connection state through the system.
324
+ this.propagateConnectionState();
333
325
  this._lifecycleState = "loaded";
334
326
  }
335
327
  }
@@ -451,19 +443,30 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
451
443
  return this.protocolHandler.quorum;
452
444
  }
453
445
  close(error) {
446
+ // 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
447
+ // 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
448
+ // handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
449
+ // "closing" will lose that info (can also solve by tracking extra state).
450
+ this._deltaManager.close(error);
451
+ (0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
452
+ (0, common_utils_1.assert)(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
453
+ }
454
+ closeCore(error) {
454
455
  var _a, _b, _c, _d;
455
- if (this.closed) {
456
- return;
457
- }
456
+ (0, common_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
458
457
  try {
459
- this._lifecycleState = "closing";
460
458
  // Ensure that we raise all key events even if one of these throws
461
459
  try {
462
- this._deltaManager.close(error);
460
+ // Raise event first, to ensure we capture _lifecycleState before transition.
461
+ // This gives us a chance to know what errors happened on open vs. on fully loaded container.
462
+ this.mc.logger.sendTelemetryEvent({
463
+ eventName: "ContainerClose",
464
+ category: error === undefined ? "generic" : "error",
465
+ }, error);
466
+ this._lifecycleState = "closing";
463
467
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
464
468
  this.connectionStateHandler.dispose();
465
469
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
466
- (0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
467
470
  (_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
468
471
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
469
472
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -473,10 +476,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
473
476
  catch (exception) {
474
477
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
475
478
  }
476
- this.mc.logger.sendTelemetryEvent({
477
- eventName: "ContainerClose",
478
- category: error === undefined ? "generic" : "error",
479
- }, error);
480
479
  this.emit("closed", error);
481
480
  this.removeAllListeners();
482
481
  if (this.visibilityEventHandler !== undefined) {
@@ -493,9 +492,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
493
492
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
494
493
  (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
495
494
  (0, common_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
495
+ (0, common_utils_1.assert)(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
496
+ (0, common_utils_1.assert)(this._protocolHandler.attributes.term !== undefined, 0x30b /* Must have a valid protocol handler instance */);
496
497
  const pendingState = {
497
498
  pendingRuntimeState: this.context.getPendingLocalState(),
498
499
  url: this.resolvedUrl.url,
500
+ protocol: this.protocolHandler.getProtocolState(),
501
+ term: this._protocolHandler.attributes.term,
502
+ clientId: this.clientId,
499
503
  };
500
504
  this.close();
501
505
  return JSON.stringify(pendingState);
@@ -539,7 +543,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
539
543
  // starting to attach the container to storage.
540
544
  // Also, this should only be fired in detached container.
541
545
  this._attachState = container_definitions_1.AttachState.Attaching;
542
- this.context.notifyAttaching();
546
+ this.context.notifyAttaching((0, utils_1.getSnapshotTreeFromSerializedContainer)(summary));
543
547
  }
544
548
  // Actually go and create the resolved document
545
549
  const createNewResolvedUrl = await this.urlResolver.resolve(request);
@@ -574,7 +578,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
574
578
  const protocolSummary = this.captureProtocolSummary();
575
579
  summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
576
580
  this._attachState = container_definitions_1.AttachState.Attaching;
577
- this.context.notifyAttaching();
581
+ this.context.notifyAttaching((0, utils_1.getSnapshotTreeFromSerializedContainer)(summary));
578
582
  await this.storageService.uploadSummaryWithContext(summary, {
579
583
  referenceSequenceNumber: 0,
580
584
  ackHandle: undefined,
@@ -604,27 +608,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
604
608
  async request(path) {
605
609
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
606
610
  }
607
- /**
608
- * Dictates whether or not the current container will automatically attempt to reconnect to the delta stream
609
- * after receiving a disconnect event
610
- * @param reconnect - Boolean indicating if reconnect should automatically occur
611
- * @deprecated - 0.58, This API will be removed in 1.0
612
- * Use `connect()` and `disconnect()` instead of `setAutoReconnect(true)` and `setAutoReconnect(false)` respectively
613
- * See https://github.com/microsoft/FluidFramework/issues/9167 for context
614
- */
615
- setAutoReconnect(reconnect) {
616
- if (this.closed) {
617
- throw new Error("Attempting to setAutoReconnect() a closed Container");
618
- }
619
- const mode = reconnect ? contracts_1.ReconnectMode.Enabled : contracts_1.ReconnectMode.Disabled;
620
- this.setAutoReconnectInternal(mode);
621
- // If container state is not attached and resumed, then don't connect to delta stream. Also don't set the
622
- // manual reconnection flag to true as we haven't made the initial connection yet.
623
- if (reconnect && this._attachState === container_definitions_1.AttachState.Attached && this.resumedOpProcessingAfterLoad) {
624
- // Ensure connection to web socket
625
- this.connectToDeltaStream({ reason: "autoReconnect" });
626
- }
627
- }
628
611
  setAutoReconnectInternal(mode) {
629
612
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
630
613
  if (currentMode === mode) {
@@ -678,22 +661,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
678
661
  const mode = contracts_1.ReconnectMode.Disabled;
679
662
  this.setAutoReconnectInternal(mode);
680
663
  }
681
- /**
682
- * Have the container attempt to resume processing ops
683
- * @deprecated - 0.58, This API will be removed in 1.0
684
- * Use `connect()` instead
685
- * See https://github.com/microsoft/FluidFramework/issues/9167 for context
686
- */
687
- resume() {
688
- if (!this.closed) {
689
- // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
690
- // If there is gap, we will learn about it once connected, but the gap should be small (if any),
691
- // assuming that resume() is called quickly after initial container boot.
692
- this.resumeInternal({ reason: "DocumentOpenResume", fetchOpsFromStorage: false });
693
- }
694
- }
695
664
  resumeInternal(args) {
696
- (0, common_utils_1.assert)(!this.closed, 0x0d9 /* "Attempting to setAutoReconnect() a closed DeltaManager" */);
665
+ (0, common_utils_1.assert)(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
697
666
  // Resume processing ops
698
667
  if (!this.resumedOpProcessingAfterLoad) {
699
668
  this.resumedOpProcessingAfterLoad = true;
@@ -782,12 +751,26 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
782
751
  if (loadMode.deltaConnection === undefined) {
783
752
  this.connectToDeltaStream(connectionArgs);
784
753
  }
785
- await this.connectStorageService();
754
+ if (!pendingLocalState) {
755
+ await this.connectStorageService();
756
+ }
757
+ else {
758
+ // if we have pendingLocalState we can load without storage; don't wait for connection
759
+ this.connectStorageService().catch((error) => this.close(error));
760
+ }
786
761
  this._attachState = container_definitions_1.AttachState.Attached;
787
762
  // Fetch specified snapshot.
788
- const { snapshot, versionId } = await this.fetchSnapshotTree(specifiedVersion);
789
- (0, common_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
790
- const attributes = await this.getDocumentAttributes(this.storageService, snapshot);
763
+ const { snapshot, versionId } = pendingLocalState === undefined
764
+ ? await this.fetchSnapshotTree(specifiedVersion)
765
+ : { snapshot: undefined, versionId: undefined };
766
+ (0, common_utils_1.assert)(snapshot !== undefined || pendingLocalState !== undefined, 0x237 /* "Snapshot should exist" */);
767
+ const attributes = pendingLocalState === undefined
768
+ ? await this.getDocumentAttributes(this.storageService, snapshot)
769
+ : {
770
+ sequenceNumber: pendingLocalState.protocol.sequenceNumber,
771
+ minimumSequenceNumber: pendingLocalState.protocol.minimumSequenceNumber,
772
+ term: pendingLocalState.term,
773
+ };
791
774
  let opsBeforeReturnP;
792
775
  // Attach op handlers to finish initialization and be able to start processing ops
793
776
  // Kick off any ops fetching if required.
@@ -808,15 +791,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
808
791
  }
809
792
  // ...load in the existing quorum
810
793
  // Initialize the protocol handler
811
- this._protocolHandler =
812
- await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
794
+ this._protocolHandler = pendingLocalState === undefined
795
+ ? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
796
+ : await this.initializeProtocolState(attributes, pendingLocalState.protocol.members, pendingLocalState.protocol.proposals, pendingLocalState.protocol.values);
813
797
  const codeDetails = this.getCodeDetailsFromQuorum();
814
798
  await this.instantiateContext(true, // existing
815
- codeDetails, snapshot, pendingLocalState);
816
- // Propagate current connection state through the system.
817
- this.propagateConnectionState();
799
+ codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
818
800
  // Internal context is fully loaded at this point
819
- this.loaded = true;
801
+ this.setLoaded();
820
802
  // We might have hit some failure that did not manifest itself in exception in this flow,
821
803
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
822
804
  if (!this.closed) {
@@ -829,7 +811,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
829
811
  }
830
812
  switch (loadMode.deltaConnection) {
831
813
  case undefined:
832
- this.resume();
814
+ // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
815
+ // If there is gap, we will learn about it once connected, but the gap should be small (if any),
816
+ // assuming that resumeInternal() is called quickly after initial container boot.
817
+ this.resumeInternal({ reason: "DocumentLoad", fetchOpsFromStorage: false });
833
818
  break;
834
819
  case "delayed":
835
820
  this.resumedOpProcessingAfterLoad = true;
@@ -869,8 +854,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
869
854
  qValues);
870
855
  // The load context - given we seeded the quorum - will be great
871
856
  await this.instantiateContextDetached(false);
872
- this.propagateConnectionState();
873
- this.loaded = true;
857
+ this.setLoaded();
874
858
  }
875
859
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
876
860
  if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
@@ -891,8 +875,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
891
875
  codeDetails !== undefined ? (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails) : []);
892
876
  await this.instantiateContextDetached(true, // existing
893
877
  snapshotTree);
894
- this.loaded = true;
895
- this.propagateConnectionState();
878
+ this.setLoaded();
896
879
  }
897
880
  async connectStorageService() {
898
881
  var _a, _b;
@@ -946,18 +929,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
946
929
  return protocolHandler;
947
930
  }
948
931
  async initializeProtocolState(attributes, members, proposals, values) {
949
- const protocol = new protocol_base_1.ProtocolOpHandler(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, { key, value }));
932
+ const protocol = new protocol_base_1.ProtocolOpHandlerWithClientValidation(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, { key, value }));
950
933
  const protocolLogger = telemetry_utils_1.ChildLogger.create(this.subLogger, "ProtocolHandler");
951
934
  protocol.quorum.on("error", (error) => {
952
935
  protocolLogger.sendErrorEvent(error);
953
936
  });
954
937
  // Track membership changes and update connection state accordingly
955
- protocol.quorum.on("addMember", (clientId, details) => {
956
- this.connectionStateHandler.receivedAddMemberEvent(clientId);
957
- });
958
- protocol.quorum.on("removeMember", (clientId) => {
959
- this.connectionStateHandler.receivedRemoveMemberEvent(clientId);
960
- });
938
+ this.connectionStateHandler.initProtocol(protocol);
961
939
  protocol.quorum.on("addProposal", (proposal) => {
962
940
  if (proposal.key === "code" || proposal.key === "code2") {
963
941
  this.emit("codeDetailsProposed", proposal.value, proposal);
@@ -979,17 +957,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
979
957
  return protocol;
980
958
  }
981
959
  captureProtocolSummary() {
982
- const quorumSnapshot = this.protocolHandler.quorum.snapshot();
983
- // Save attributes for the document
984
- const documentAttributes = {
985
- minimumSequenceNumber: this.protocolHandler.minimumSequenceNumber,
986
- sequenceNumber: this.protocolHandler.sequenceNumber,
987
- term: this.protocolHandler.term,
988
- };
960
+ const quorumSnapshot = this.protocolHandler.snapshot();
989
961
  const summary = {
990
962
  tree: {
991
963
  attributes: {
992
- content: JSON.stringify(documentAttributes),
964
+ content: JSON.stringify(this.protocolHandler.attributes),
993
965
  type: protocol_definitions_1.SummaryType.Blob,
994
966
  },
995
967
  quorumMembers: {
@@ -1078,7 +1050,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1078
1050
  this.emit("readonly", readonly);
1079
1051
  });
1080
1052
  deltaManager.on("closed", (error) => {
1081
- this.close(error);
1053
+ this.closeCore(error);
1082
1054
  });
1083
1055
  return deltaManager;
1084
1056
  }
@@ -1092,6 +1064,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1092
1064
  }, prefetchType);
1093
1065
  }
1094
1066
  logConnectionStateChangeTelemetry(value, oldState, reason) {
1067
+ var _a;
1095
1068
  // Log actual event
1096
1069
  const time = common_utils_1.performance.now();
1097
1070
  this.connectionTransitionTimes[value] = time;
@@ -1127,12 +1100,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1127
1100
  durationFromDisconnected,
1128
1101
  reason,
1129
1102
  connectionInitiationReason, pendingClientId: this.connectionStateHandler.pendingClientId, clientId: this.clientId, autoReconnect,
1130
- opsBehind, online: driver_utils_1.OnlineStatus[(0, driver_utils_1.isOnline)()], lastVisible: this.lastVisible !== undefined ? common_utils_1.performance.now() - this.lastVisible : undefined, checkpointSequenceNumber }, this._deltaManager.connectionProps));
1103
+ opsBehind, online: driver_utils_1.OnlineStatus[(0, driver_utils_1.isOnline)()], lastVisible: this.lastVisible !== undefined ? common_utils_1.performance.now() - this.lastVisible : undefined, checkpointSequenceNumber, quorumSize: (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum.getMembers().size }, this._deltaManager.connectionProps));
1131
1104
  if (value === connectionState_1.ConnectionState.Connected) {
1132
1105
  this.firstConnection = false;
1133
1106
  }
1134
1107
  }
1135
1108
  propagateConnectionState() {
1109
+ var _a;
1136
1110
  const logOpsOnReconnect = this.connectionState === connectionState_1.ConnectionState.Connected &&
1137
1111
  !this.firstConnection &&
1138
1112
  this.connectionMode === "write";
@@ -1140,11 +1114,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1140
1114
  this.messageCountAfterDisconnection = 0;
1141
1115
  }
1142
1116
  const state = this.connectionState === connectionState_1.ConnectionState.Connected;
1143
- if (!this.context.disposed) {
1117
+ // Both protocol and context should not be undefined if we got so far.
1118
+ if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1144
1119
  this.context.setConnectionState(state, this.clientId);
1145
1120
  }
1146
- (0, common_utils_1.assert)(this.protocolHandler !== undefined, 0x0dc /* "Protocol handler should be set here" */);
1147
- this.protocolHandler.quorum.setConnectionState(state, this.clientId);
1121
+ this.protocolHandler.setConnectionState(state, this.clientId);
1148
1122
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId);
1149
1123
  if (logOpsOnReconnect) {
1150
1124
  this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
@@ -1186,31 +1160,19 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1186
1160
  return this._deltaManager.submit(type, contents, batch, metadata);
1187
1161
  }
1188
1162
  processRemoteMessage(message) {
1189
- // Check and report if we're getting messages from a clientId that we previously
1190
- // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
1191
- if (message.clientId != null) {
1192
- let errorMsg;
1193
- const client = this.getQuorum().getMember(message.clientId);
1194
- if (client === undefined && message.type !== protocol_definitions_1.MessageType.ClientJoin) {
1195
- // pre-0.58 error message: messageClientIdMissingFromQuorum
1196
- errorMsg = "Remote message's clientId is missing from the quorum";
1197
- }
1198
- else if ((client === null || client === void 0 ? void 0 : client.shouldHaveLeft) === true && message.type !== protocol_definitions_1.MessageType.NoOp) {
1199
- // pre-0.58 error message: messageClientIdShouldHaveLeft
1200
- errorMsg = "Remote message's clientId already should have left";
1201
- }
1202
- if (errorMsg !== undefined) {
1203
- const error = new container_utils_1.DataCorruptionError(errorMsg, (0, container_utils_1.extractSafePropertiesFromMessage)(message));
1204
- this.close((0, telemetry_utils_1.normalizeError)(error));
1205
- }
1206
- }
1207
1163
  const local = this.clientId === message.clientId;
1164
+ // Allow the protocol handler to process the message
1165
+ let result = { immediateNoOp: false };
1166
+ try {
1167
+ result = this.protocolHandler.processMessage(message, local);
1168
+ }
1169
+ catch (error) {
1170
+ this.close((0, telemetry_utils_1.wrapError)(error, (errorMessage) => new container_utils_1.DataCorruptionError(errorMessage, (0, container_utils_1.extractSafePropertiesFromMessage)(message))));
1171
+ }
1208
1172
  // Forward non system messages to the loaded runtime for processing
1209
1173
  if (!(0, protocol_base_1.isSystemMessage)(message)) {
1210
1174
  this.context.process(message, local, undefined);
1211
1175
  }
1212
- // Allow the protocol handler to process the message
1213
- const result = this.protocolHandler.processMessage(message, local);
1214
1176
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1215
1177
  if (this.activeConnection()) {
1216
1178
  if (this.collabWindowTracker === undefined) {
@@ -1218,35 +1180,17 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1218
1180
  // That means that if relay service changes settings, such changes will impact only newly booted
1219
1181
  // clients.
1220
1182
  // All existing will continue to use settings they got earlier.
1221
- const [noopTimeFrequency, noopCountFrequency] = this.getNoopConfig();
1183
+ (0, common_utils_1.assert)(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1222
1184
  this.collabWindowTracker = new collabWindowTracker_1.CollabWindowTracker((type, contents) => {
1223
1185
  (0, common_utils_1.assert)(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1224
1186
  this.submitMessage(type, contents);
1225
- }, noopTimeFrequency, noopCountFrequency);
1187
+ }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1226
1188
  }
1227
1189
  this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1228
1190
  }
1229
1191
  this.emit("op", message);
1230
1192
  return result;
1231
1193
  }
1232
- /**
1233
- * #260 (ADO)
1234
- * back-compat: noopTimeFrequency & noopCountFrequency properties were added to
1235
- * IClientConfiguration in 0.59.3000. During the integration, we must read the
1236
- * available configuration from the loader options.
1237
- */
1238
- getNoopConfig() {
1239
- var _a, _b;
1240
- (0, common_utils_1.assert)(this.serviceConfiguration !== undefined, 0x2e2);
1241
- if (this.serviceConfiguration.noopTimeFrequency !== undefined ||
1242
- this.serviceConfiguration.noopCountFrequency !== undefined) {
1243
- return [
1244
- this.serviceConfiguration.noopTimeFrequency,
1245
- this.serviceConfiguration.noopCountFrequency,
1246
- ];
1247
- }
1248
- return [(_a = this.loader.services.options) === null || _a === void 0 ? void 0 : _a.noopTimeFrequency, (_b = this.loader.services.options) === null || _b === void 0 ? void 0 : _b.noopCountFrequency];
1249
- }
1250
1194
  submitSignal(message) {
1251
1195
  this._deltaManager.submitSignal(JSON.stringify(message));
1252
1196
  }
@@ -1287,12 +1231,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1287
1231
  }
1288
1232
  return { snapshot, versionId: version === null || version === void 0 ? void 0 : version.id };
1289
1233
  }
1290
- async instantiateContextDetached(existing, snapshot, pendingLocalState) {
1234
+ async instantiateContextDetached(existing, snapshot) {
1291
1235
  const codeDetails = this.getCodeDetailsFromQuorum();
1292
1236
  if (codeDetails === undefined) {
1293
1237
  throw new Error("pkg should be provided in create flow!!");
1294
1238
  }
1295
- await this.instantiateContext(existing, codeDetails, snapshot, pendingLocalState);
1239
+ await this.instantiateContext(existing, codeDetails, snapshot);
1296
1240
  }
1297
1241
  async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1298
1242
  var _a;