@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/lib/container.js CHANGED
@@ -9,7 +9,7 @@ import { assert, performance, unreachableCase } from "@fluidframework/common-uti
9
9
  import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
10
10
  import { DataCorruptionError, extractSafePropertiesFromMessage, GenericError, UsageError, } from "@fluidframework/container-utils";
11
11
  import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
12
- import { isSystemMessage, ProtocolOpHandler, } from "@fluidframework/protocol-base";
12
+ import { isSystemMessage, ProtocolOpHandlerWithClientValidation, } from "@fluidframework/protocol-base";
13
13
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
14
14
  import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
15
15
  import { Audience } from "./audience";
@@ -36,7 +36,7 @@ const savedContainerEvent = "saved";
36
36
  * Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
37
37
  * up to date. Host may chose to wait in such case and retry resolving URI.
38
38
  * Warning: Will wait infinitely for connection to establish if there is no connection.
39
- * May result in deadlock if Container.setAutoReconnect(false) is called and never switched back to auto-reconnect.
39
+ * May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
40
40
  * @returns true: container is up to date, it processed all the ops that were know at the time of first connection
41
41
  * false: storage does not provide indication of how far the client is. Container processed
42
42
  * all the ops known to it, but it maybe still behind.
@@ -58,7 +58,8 @@ export async function waitContainerToCatchUp(container) {
58
58
  };
59
59
  container.on("closed", closedCallback);
60
60
  const waitForOps = () => {
61
- assert(container.connectionState !== ConnectionState.Disconnected, 0x0cd /* "Container disconnected while waiting for ops!" */);
61
+ assert(container.connectionState === ConnectionState.CatchingUp
62
+ || container.connectionState === ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
62
63
  const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
63
64
  const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
64
65
  assert(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
@@ -89,9 +90,7 @@ export async function waitContainerToCatchUp(container) {
89
90
  waitForOps();
90
91
  };
91
92
  container.on(connectedEventName, callback);
92
- // TODO: Remove null check after next release #8523
93
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
94
- container.resume();
93
+ container.connect();
95
94
  });
96
95
  }
97
96
  const getCodeProposal =
@@ -100,7 +99,7 @@ const getCodeProposal =
100
99
  const summarizerClientType = "summarizer";
101
100
  export class Container extends EventEmitterWithErrorHandling {
102
101
  constructor(loader, config) {
103
- var _a;
102
+ var _a, _b;
104
103
  super((name, error) => {
105
104
  this.mc.logger.sendErrorEvent({
106
105
  eventName: "ContainerEventHandlerException",
@@ -111,7 +110,7 @@ export class Container extends EventEmitterWithErrorHandling {
111
110
  // Tells if container can reconnect on losing fist connection
112
111
  // If false, container gets closed on loss of connection.
113
112
  this._canReconnect = true;
114
- this._lifecycleState = "created";
113
+ this._lifecycleState = "loading";
115
114
  this._attachState = AttachState.Detached;
116
115
  this.resumedOpProcessingAfterLoad = false;
117
116
  this.firstConnection = true;
@@ -167,20 +166,18 @@ export class Container extends EventEmitterWithErrorHandling {
167
166
  logConnectionStateChangeTelemetry: (value, oldState, reason) => this.logConnectionStateChangeTelemetry(value, oldState, reason),
168
167
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
169
168
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
170
- logConnectionIssue: (eventName) => {
169
+ logConnectionIssue: (eventName, details) => {
171
170
  // We get here when socket does not receive any ops on "write" connection, including
172
171
  // its own join op. Attempt recovery option.
173
- this._deltaManager.logConnectionIssue({
174
- eventName,
175
- duration: performance.now() - this.connectionTransitionTimes[ConnectionState.Connecting],
176
- });
172
+ this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: performance.now() - this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
177
173
  },
178
174
  connectionStateChanged: () => {
179
- if (this.loaded) {
175
+ // Fire events only if container is fully loaded and not closed
176
+ if (this._lifecycleState === "loaded") {
180
177
  this.propagateConnectionState();
181
178
  }
182
179
  },
183
- }, this.mc.logger);
180
+ }, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
184
181
  this.on(savedContainerEvent, () => {
185
182
  this.connectionStateHandler.containerSaved();
186
183
  });
@@ -258,16 +255,16 @@ export class Container extends EventEmitterWithErrorHandling {
258
255
  clientDetailsOverride: loadOptions.clientDetailsOverride,
259
256
  resolvedUrl: loadOptions.resolvedUrl,
260
257
  canReconnect: loadOptions.canReconnect,
258
+ serializedContainerState: pendingLocalState,
261
259
  });
262
260
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
263
- var _a;
264
- container._lifecycleState = "loading";
261
+ var _a, _b;
265
262
  const version = loadOptions.version;
266
- // always load unpaused with pending ops!
267
- // It is also default mode in general.
268
263
  const defaultMode = { opsBeforeReturn: "cached" };
269
- assert(pendingLocalState === undefined || loadOptions.loadMode === undefined, 0x1e1 /* "pending state requires immediate connection!" */);
270
- const mode = (_a = loadOptions.loadMode) !== null && _a !== void 0 ? _a : defaultMode;
264
+ // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
265
+ // to return container, so ignore this value and use undefined for opsBeforeReturn
266
+ const mode = pendingLocalState
267
+ ? Object.assign(Object.assign({}, ((_a = loadOptions.loadMode) !== null && _a !== void 0 ? _a : defaultMode)), { opsBeforeReturn: undefined }) : (_b = loadOptions.loadMode) !== null && _b !== void 0 ? _b : defaultMode;
271
268
  const onClosed = (err) => {
272
269
  // pre-0.58 error message: containerClosedWithoutErrorDuringLoad
273
270
  reject(err !== null && err !== void 0 ? err : new GenericError("Container closed without error during load"));
@@ -296,7 +293,6 @@ export class Container extends EventEmitterWithErrorHandling {
296
293
  static async createDetached(loader, codeDetails) {
297
294
  const container = new Container(loader, {});
298
295
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
299
- container._lifecycleState = "loading";
300
296
  await container.createDetached(codeDetails);
301
297
  return container;
302
298
  }, { start: true, end: true, cancel: "generic" });
@@ -309,20 +305,16 @@ export class Container extends EventEmitterWithErrorHandling {
309
305
  const container = new Container(loader, {});
310
306
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
311
307
  const deserializedSummary = JSON.parse(snapshot);
312
- container._lifecycleState = "loading";
313
308
  await container.rehydrateDetachedFromSnapshot(deserializedSummary);
314
309
  return container;
315
310
  }, { start: true, end: true, cancel: "generic" });
316
311
  }
317
- get loaded() {
318
- return (this._lifecycleState !== "created" && this._lifecycleState !== "loading");
319
- }
320
- set loaded(t) {
321
- assert(t, 0x27d /* "Setting loaded state to false is not supported" */);
322
- assert(this._lifecycleState !== "created", 0x27e /* "Must go through loading state before loaded" */);
312
+ setLoaded() {
323
313
  // It's conceivable the container could be closed when this is called
324
314
  // Only transition states if currently loading
325
315
  if (this._lifecycleState === "loading") {
316
+ // Propagate current connection state through the system.
317
+ this.propagateConnectionState();
326
318
  this._lifecycleState = "loaded";
327
319
  }
328
320
  }
@@ -444,19 +436,30 @@ export class Container extends EventEmitterWithErrorHandling {
444
436
  return this.protocolHandler.quorum;
445
437
  }
446
438
  close(error) {
439
+ // 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
440
+ // 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
441
+ // handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
442
+ // "closing" will lose that info (can also solve by tracking extra state).
443
+ this._deltaManager.close(error);
444
+ assert(this.connectionState === ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
445
+ assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
446
+ }
447
+ closeCore(error) {
447
448
  var _a, _b, _c, _d;
448
- if (this.closed) {
449
- return;
450
- }
449
+ assert(!this.closed, 0x315 /* re-entrancy */);
451
450
  try {
452
- this._lifecycleState = "closing";
453
451
  // Ensure that we raise all key events even if one of these throws
454
452
  try {
455
- this._deltaManager.close(error);
453
+ // Raise event first, to ensure we capture _lifecycleState before transition.
454
+ // This gives us a chance to know what errors happened on open vs. on fully loaded container.
455
+ this.mc.logger.sendTelemetryEvent({
456
+ eventName: "ContainerClose",
457
+ category: error === undefined ? "generic" : "error",
458
+ }, error);
459
+ this._lifecycleState = "closing";
456
460
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
457
461
  this.connectionStateHandler.dispose();
458
462
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
459
- assert(this.connectionState === ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
460
463
  (_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
461
464
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
462
465
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -466,10 +469,6 @@ export class Container extends EventEmitterWithErrorHandling {
466
469
  catch (exception) {
467
470
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
468
471
  }
469
- this.mc.logger.sendTelemetryEvent({
470
- eventName: "ContainerClose",
471
- category: error === undefined ? "generic" : "error",
472
- }, error);
473
472
  this.emit("closed", error);
474
473
  this.removeAllListeners();
475
474
  if (this.visibilityEventHandler !== undefined) {
@@ -486,9 +485,14 @@ export class Container extends EventEmitterWithErrorHandling {
486
485
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
487
486
  assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
488
487
  assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
488
+ assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
489
+ assert(this._protocolHandler.attributes.term !== undefined, 0x30b /* Must have a valid protocol handler instance */);
489
490
  const pendingState = {
490
491
  pendingRuntimeState: this.context.getPendingLocalState(),
491
492
  url: this.resolvedUrl.url,
493
+ protocol: this.protocolHandler.getProtocolState(),
494
+ term: this._protocolHandler.attributes.term,
495
+ clientId: this.clientId,
492
496
  };
493
497
  this.close();
494
498
  return JSON.stringify(pendingState);
@@ -532,7 +536,7 @@ export class Container extends EventEmitterWithErrorHandling {
532
536
  // starting to attach the container to storage.
533
537
  // Also, this should only be fired in detached container.
534
538
  this._attachState = AttachState.Attaching;
535
- this.context.notifyAttaching();
539
+ this.context.notifyAttaching(getSnapshotTreeFromSerializedContainer(summary));
536
540
  }
537
541
  // Actually go and create the resolved document
538
542
  const createNewResolvedUrl = await this.urlResolver.resolve(request);
@@ -567,7 +571,7 @@ export class Container extends EventEmitterWithErrorHandling {
567
571
  const protocolSummary = this.captureProtocolSummary();
568
572
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
569
573
  this._attachState = AttachState.Attaching;
570
- this.context.notifyAttaching();
574
+ this.context.notifyAttaching(getSnapshotTreeFromSerializedContainer(summary));
571
575
  await this.storageService.uploadSummaryWithContext(summary, {
572
576
  referenceSequenceNumber: 0,
573
577
  ackHandle: undefined,
@@ -597,27 +601,6 @@ export class Container extends EventEmitterWithErrorHandling {
597
601
  async request(path) {
598
602
  return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
599
603
  }
600
- /**
601
- * Dictates whether or not the current container will automatically attempt to reconnect to the delta stream
602
- * after receiving a disconnect event
603
- * @param reconnect - Boolean indicating if reconnect should automatically occur
604
- * @deprecated - 0.58, This API will be removed in 1.0
605
- * Use `connect()` and `disconnect()` instead of `setAutoReconnect(true)` and `setAutoReconnect(false)` respectively
606
- * See https://github.com/microsoft/FluidFramework/issues/9167 for context
607
- */
608
- setAutoReconnect(reconnect) {
609
- if (this.closed) {
610
- throw new Error("Attempting to setAutoReconnect() a closed Container");
611
- }
612
- const mode = reconnect ? ReconnectMode.Enabled : ReconnectMode.Disabled;
613
- this.setAutoReconnectInternal(mode);
614
- // If container state is not attached and resumed, then don't connect to delta stream. Also don't set the
615
- // manual reconnection flag to true as we haven't made the initial connection yet.
616
- if (reconnect && this._attachState === AttachState.Attached && this.resumedOpProcessingAfterLoad) {
617
- // Ensure connection to web socket
618
- this.connectToDeltaStream({ reason: "autoReconnect" });
619
- }
620
- }
621
604
  setAutoReconnectInternal(mode) {
622
605
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
623
606
  if (currentMode === mode) {
@@ -671,22 +654,8 @@ export class Container extends EventEmitterWithErrorHandling {
671
654
  const mode = ReconnectMode.Disabled;
672
655
  this.setAutoReconnectInternal(mode);
673
656
  }
674
- /**
675
- * Have the container attempt to resume processing ops
676
- * @deprecated - 0.58, This API will be removed in 1.0
677
- * Use `connect()` instead
678
- * See https://github.com/microsoft/FluidFramework/issues/9167 for context
679
- */
680
- resume() {
681
- if (!this.closed) {
682
- // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
683
- // If there is gap, we will learn about it once connected, but the gap should be small (if any),
684
- // assuming that resume() is called quickly after initial container boot.
685
- this.resumeInternal({ reason: "DocumentOpenResume", fetchOpsFromStorage: false });
686
- }
687
- }
688
657
  resumeInternal(args) {
689
- assert(!this.closed, 0x0d9 /* "Attempting to setAutoReconnect() a closed DeltaManager" */);
658
+ assert(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
690
659
  // Resume processing ops
691
660
  if (!this.resumedOpProcessingAfterLoad) {
692
661
  this.resumedOpProcessingAfterLoad = true;
@@ -775,12 +744,26 @@ export class Container extends EventEmitterWithErrorHandling {
775
744
  if (loadMode.deltaConnection === undefined) {
776
745
  this.connectToDeltaStream(connectionArgs);
777
746
  }
778
- await this.connectStorageService();
747
+ if (!pendingLocalState) {
748
+ await this.connectStorageService();
749
+ }
750
+ else {
751
+ // if we have pendingLocalState we can load without storage; don't wait for connection
752
+ this.connectStorageService().catch((error) => this.close(error));
753
+ }
779
754
  this._attachState = AttachState.Attached;
780
755
  // Fetch specified snapshot.
781
- const { snapshot, versionId } = await this.fetchSnapshotTree(specifiedVersion);
782
- assert(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
783
- const attributes = await this.getDocumentAttributes(this.storageService, snapshot);
756
+ const { snapshot, versionId } = pendingLocalState === undefined
757
+ ? await this.fetchSnapshotTree(specifiedVersion)
758
+ : { snapshot: undefined, versionId: undefined };
759
+ assert(snapshot !== undefined || pendingLocalState !== undefined, 0x237 /* "Snapshot should exist" */);
760
+ const attributes = pendingLocalState === undefined
761
+ ? await this.getDocumentAttributes(this.storageService, snapshot)
762
+ : {
763
+ sequenceNumber: pendingLocalState.protocol.sequenceNumber,
764
+ minimumSequenceNumber: pendingLocalState.protocol.minimumSequenceNumber,
765
+ term: pendingLocalState.term,
766
+ };
784
767
  let opsBeforeReturnP;
785
768
  // Attach op handlers to finish initialization and be able to start processing ops
786
769
  // Kick off any ops fetching if required.
@@ -801,15 +784,14 @@ export class Container extends EventEmitterWithErrorHandling {
801
784
  }
802
785
  // ...load in the existing quorum
803
786
  // Initialize the protocol handler
804
- this._protocolHandler =
805
- await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
787
+ this._protocolHandler = pendingLocalState === undefined
788
+ ? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
789
+ : await this.initializeProtocolState(attributes, pendingLocalState.protocol.members, pendingLocalState.protocol.proposals, pendingLocalState.protocol.values);
806
790
  const codeDetails = this.getCodeDetailsFromQuorum();
807
791
  await this.instantiateContext(true, // existing
808
- codeDetails, snapshot, pendingLocalState);
809
- // Propagate current connection state through the system.
810
- this.propagateConnectionState();
792
+ codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
811
793
  // Internal context is fully loaded at this point
812
- this.loaded = true;
794
+ this.setLoaded();
813
795
  // We might have hit some failure that did not manifest itself in exception in this flow,
814
796
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
815
797
  if (!this.closed) {
@@ -822,7 +804,10 @@ export class Container extends EventEmitterWithErrorHandling {
822
804
  }
823
805
  switch (loadMode.deltaConnection) {
824
806
  case undefined:
825
- this.resume();
807
+ // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
808
+ // If there is gap, we will learn about it once connected, but the gap should be small (if any),
809
+ // assuming that resumeInternal() is called quickly after initial container boot.
810
+ this.resumeInternal({ reason: "DocumentLoad", fetchOpsFromStorage: false });
826
811
  break;
827
812
  case "delayed":
828
813
  this.resumedOpProcessingAfterLoad = true;
@@ -862,8 +847,7 @@ export class Container extends EventEmitterWithErrorHandling {
862
847
  qValues);
863
848
  // The load context - given we seeded the quorum - will be great
864
849
  await this.instantiateContextDetached(false);
865
- this.propagateConnectionState();
866
- this.loaded = true;
850
+ this.setLoaded();
867
851
  }
868
852
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
869
853
  if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
@@ -884,8 +868,7 @@ export class Container extends EventEmitterWithErrorHandling {
884
868
  codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : []);
885
869
  await this.instantiateContextDetached(true, // existing
886
870
  snapshotTree);
887
- this.loaded = true;
888
- this.propagateConnectionState();
871
+ this.setLoaded();
889
872
  }
890
873
  async connectStorageService() {
891
874
  var _a, _b;
@@ -939,18 +922,13 @@ export class Container extends EventEmitterWithErrorHandling {
939
922
  return protocolHandler;
940
923
  }
941
924
  async initializeProtocolState(attributes, members, proposals, values) {
942
- const protocol = new ProtocolOpHandler(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(MessageType.Propose, { key, value }));
925
+ const protocol = new ProtocolOpHandlerWithClientValidation(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(MessageType.Propose, { key, value }));
943
926
  const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
944
927
  protocol.quorum.on("error", (error) => {
945
928
  protocolLogger.sendErrorEvent(error);
946
929
  });
947
930
  // Track membership changes and update connection state accordingly
948
- protocol.quorum.on("addMember", (clientId, details) => {
949
- this.connectionStateHandler.receivedAddMemberEvent(clientId);
950
- });
951
- protocol.quorum.on("removeMember", (clientId) => {
952
- this.connectionStateHandler.receivedRemoveMemberEvent(clientId);
953
- });
931
+ this.connectionStateHandler.initProtocol(protocol);
954
932
  protocol.quorum.on("addProposal", (proposal) => {
955
933
  if (proposal.key === "code" || proposal.key === "code2") {
956
934
  this.emit("codeDetailsProposed", proposal.value, proposal);
@@ -972,17 +950,11 @@ export class Container extends EventEmitterWithErrorHandling {
972
950
  return protocol;
973
951
  }
974
952
  captureProtocolSummary() {
975
- const quorumSnapshot = this.protocolHandler.quorum.snapshot();
976
- // Save attributes for the document
977
- const documentAttributes = {
978
- minimumSequenceNumber: this.protocolHandler.minimumSequenceNumber,
979
- sequenceNumber: this.protocolHandler.sequenceNumber,
980
- term: this.protocolHandler.term,
981
- };
953
+ const quorumSnapshot = this.protocolHandler.snapshot();
982
954
  const summary = {
983
955
  tree: {
984
956
  attributes: {
985
- content: JSON.stringify(documentAttributes),
957
+ content: JSON.stringify(this.protocolHandler.attributes),
986
958
  type: SummaryType.Blob,
987
959
  },
988
960
  quorumMembers: {
@@ -1071,7 +1043,7 @@ export class Container extends EventEmitterWithErrorHandling {
1071
1043
  this.emit("readonly", readonly);
1072
1044
  });
1073
1045
  deltaManager.on("closed", (error) => {
1074
- this.close(error);
1046
+ this.closeCore(error);
1075
1047
  });
1076
1048
  return deltaManager;
1077
1049
  }
@@ -1085,6 +1057,7 @@ export class Container extends EventEmitterWithErrorHandling {
1085
1057
  }, prefetchType);
1086
1058
  }
1087
1059
  logConnectionStateChangeTelemetry(value, oldState, reason) {
1060
+ var _a;
1088
1061
  // Log actual event
1089
1062
  const time = performance.now();
1090
1063
  this.connectionTransitionTimes[value] = time;
@@ -1120,12 +1093,13 @@ export class Container extends EventEmitterWithErrorHandling {
1120
1093
  durationFromDisconnected,
1121
1094
  reason,
1122
1095
  connectionInitiationReason, pendingClientId: this.connectionStateHandler.pendingClientId, clientId: this.clientId, autoReconnect,
1123
- opsBehind, online: OnlineStatus[isOnline()], lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined, checkpointSequenceNumber }, this._deltaManager.connectionProps));
1096
+ opsBehind, online: OnlineStatus[isOnline()], lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined, checkpointSequenceNumber, quorumSize: (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum.getMembers().size }, this._deltaManager.connectionProps));
1124
1097
  if (value === ConnectionState.Connected) {
1125
1098
  this.firstConnection = false;
1126
1099
  }
1127
1100
  }
1128
1101
  propagateConnectionState() {
1102
+ var _a;
1129
1103
  const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
1130
1104
  !this.firstConnection &&
1131
1105
  this.connectionMode === "write";
@@ -1133,11 +1107,11 @@ export class Container extends EventEmitterWithErrorHandling {
1133
1107
  this.messageCountAfterDisconnection = 0;
1134
1108
  }
1135
1109
  const state = this.connectionState === ConnectionState.Connected;
1136
- if (!this.context.disposed) {
1110
+ // Both protocol and context should not be undefined if we got so far.
1111
+ if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1137
1112
  this.context.setConnectionState(state, this.clientId);
1138
1113
  }
1139
- assert(this.protocolHandler !== undefined, 0x0dc /* "Protocol handler should be set here" */);
1140
- this.protocolHandler.quorum.setConnectionState(state, this.clientId);
1114
+ this.protocolHandler.setConnectionState(state, this.clientId);
1141
1115
  raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
1142
1116
  if (logOpsOnReconnect) {
1143
1117
  this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
@@ -1179,31 +1153,19 @@ export class Container extends EventEmitterWithErrorHandling {
1179
1153
  return this._deltaManager.submit(type, contents, batch, metadata);
1180
1154
  }
1181
1155
  processRemoteMessage(message) {
1182
- // Check and report if we're getting messages from a clientId that we previously
1183
- // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
1184
- if (message.clientId != null) {
1185
- let errorMsg;
1186
- const client = this.getQuorum().getMember(message.clientId);
1187
- if (client === undefined && message.type !== MessageType.ClientJoin) {
1188
- // pre-0.58 error message: messageClientIdMissingFromQuorum
1189
- errorMsg = "Remote message's clientId is missing from the quorum";
1190
- }
1191
- else if ((client === null || client === void 0 ? void 0 : client.shouldHaveLeft) === true && message.type !== MessageType.NoOp) {
1192
- // pre-0.58 error message: messageClientIdShouldHaveLeft
1193
- errorMsg = "Remote message's clientId already should have left";
1194
- }
1195
- if (errorMsg !== undefined) {
1196
- const error = new DataCorruptionError(errorMsg, extractSafePropertiesFromMessage(message));
1197
- this.close(normalizeError(error));
1198
- }
1199
- }
1200
1156
  const local = this.clientId === message.clientId;
1157
+ // Allow the protocol handler to process the message
1158
+ let result = { immediateNoOp: false };
1159
+ try {
1160
+ result = this.protocolHandler.processMessage(message, local);
1161
+ }
1162
+ catch (error) {
1163
+ this.close(wrapError(error, (errorMessage) => new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
1164
+ }
1201
1165
  // Forward non system messages to the loaded runtime for processing
1202
1166
  if (!isSystemMessage(message)) {
1203
1167
  this.context.process(message, local, undefined);
1204
1168
  }
1205
- // Allow the protocol handler to process the message
1206
- const result = this.protocolHandler.processMessage(message, local);
1207
1169
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1208
1170
  if (this.activeConnection()) {
1209
1171
  if (this.collabWindowTracker === undefined) {
@@ -1211,35 +1173,17 @@ export class Container extends EventEmitterWithErrorHandling {
1211
1173
  // That means that if relay service changes settings, such changes will impact only newly booted
1212
1174
  // clients.
1213
1175
  // All existing will continue to use settings they got earlier.
1214
- const [noopTimeFrequency, noopCountFrequency] = this.getNoopConfig();
1176
+ assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1215
1177
  this.collabWindowTracker = new CollabWindowTracker((type, contents) => {
1216
1178
  assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1217
1179
  this.submitMessage(type, contents);
1218
- }, noopTimeFrequency, noopCountFrequency);
1180
+ }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1219
1181
  }
1220
1182
  this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1221
1183
  }
1222
1184
  this.emit("op", message);
1223
1185
  return result;
1224
1186
  }
1225
- /**
1226
- * #260 (ADO)
1227
- * back-compat: noopTimeFrequency & noopCountFrequency properties were added to
1228
- * IClientConfiguration in 0.59.3000. During the integration, we must read the
1229
- * available configuration from the loader options.
1230
- */
1231
- getNoopConfig() {
1232
- var _a, _b;
1233
- assert(this.serviceConfiguration !== undefined, 0x2e2);
1234
- if (this.serviceConfiguration.noopTimeFrequency !== undefined ||
1235
- this.serviceConfiguration.noopCountFrequency !== undefined) {
1236
- return [
1237
- this.serviceConfiguration.noopTimeFrequency,
1238
- this.serviceConfiguration.noopCountFrequency,
1239
- ];
1240
- }
1241
- 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];
1242
- }
1243
1187
  submitSignal(message) {
1244
1188
  this._deltaManager.submitSignal(JSON.stringify(message));
1245
1189
  }
@@ -1280,12 +1224,12 @@ export class Container extends EventEmitterWithErrorHandling {
1280
1224
  }
1281
1225
  return { snapshot, versionId: version === null || version === void 0 ? void 0 : version.id };
1282
1226
  }
1283
- async instantiateContextDetached(existing, snapshot, pendingLocalState) {
1227
+ async instantiateContextDetached(existing, snapshot) {
1284
1228
  const codeDetails = this.getCodeDetailsFromQuorum();
1285
1229
  if (codeDetails === undefined) {
1286
1230
  throw new Error("pkg should be provided in create flow!!");
1287
1231
  }
1288
- await this.instantiateContext(existing, codeDetails, snapshot, pendingLocalState);
1232
+ await this.instantiateContext(existing, codeDetails, snapshot);
1289
1233
  }
1290
1234
  async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1291
1235
  var _a;