@fluidframework/container-loader 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.1.0.148229

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 (112) hide show
  1. package/README.md +7 -4
  2. package/closeAndGetPendingLocalState.md +51 -0
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +43 -11
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionStateHandler.d.ts +4 -4
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +7 -0
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts +44 -4
  11. package/dist/container.d.ts.map +1 -1
  12. package/dist/container.js +152 -93
  13. package/dist/container.js.map +1 -1
  14. package/dist/containerContext.d.ts +18 -8
  15. package/dist/containerContext.d.ts.map +1 -1
  16. package/dist/containerContext.js +47 -4
  17. package/dist/containerContext.js.map +1 -1
  18. package/dist/containerStorageAdapter.d.ts +41 -2
  19. package/dist/containerStorageAdapter.d.ts.map +1 -1
  20. package/dist/containerStorageAdapter.js +87 -11
  21. package/dist/containerStorageAdapter.js.map +1 -1
  22. package/dist/contracts.d.ts +2 -2
  23. package/dist/contracts.d.ts.map +1 -1
  24. package/dist/contracts.js.map +1 -1
  25. package/dist/deltaManager.d.ts +3 -2
  26. package/dist/deltaManager.d.ts.map +1 -1
  27. package/dist/deltaManager.js +4 -2
  28. package/dist/deltaManager.js.map +1 -1
  29. package/dist/deltaManagerProxy.d.ts +10 -22
  30. package/dist/deltaManagerProxy.d.ts.map +1 -1
  31. package/dist/deltaManagerProxy.js +14 -50
  32. package/dist/deltaManagerProxy.js.map +1 -1
  33. package/dist/index.d.ts +3 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -3
  36. package/dist/index.js.map +1 -1
  37. package/dist/loader.d.ts +10 -1
  38. package/dist/loader.d.ts.map +1 -1
  39. package/dist/loader.js +22 -16
  40. package/dist/loader.js.map +1 -1
  41. package/dist/packageVersion.d.ts +1 -1
  42. package/dist/packageVersion.js +1 -1
  43. package/dist/packageVersion.js.map +1 -1
  44. package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
  45. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  46. package/dist/protocolTreeDocumentStorageService.js +7 -4
  47. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  48. package/dist/utils.d.ts.map +1 -1
  49. package/dist/utils.js +2 -1
  50. package/dist/utils.js.map +1 -1
  51. package/lib/connectionManager.d.ts.map +1 -1
  52. package/lib/connectionManager.js +44 -12
  53. package/lib/connectionManager.js.map +1 -1
  54. package/lib/connectionStateHandler.d.ts +4 -4
  55. package/lib/connectionStateHandler.d.ts.map +1 -1
  56. package/lib/connectionStateHandler.js +7 -0
  57. package/lib/connectionStateHandler.js.map +1 -1
  58. package/lib/container.d.ts +44 -4
  59. package/lib/container.d.ts.map +1 -1
  60. package/lib/container.js +155 -96
  61. package/lib/container.js.map +1 -1
  62. package/lib/containerContext.d.ts +18 -8
  63. package/lib/containerContext.d.ts.map +1 -1
  64. package/lib/containerContext.js +48 -5
  65. package/lib/containerContext.js.map +1 -1
  66. package/lib/containerStorageAdapter.d.ts +41 -2
  67. package/lib/containerStorageAdapter.d.ts.map +1 -1
  68. package/lib/containerStorageAdapter.js +85 -11
  69. package/lib/containerStorageAdapter.js.map +1 -1
  70. package/lib/contracts.d.ts +2 -2
  71. package/lib/contracts.d.ts.map +1 -1
  72. package/lib/contracts.js.map +1 -1
  73. package/lib/deltaManager.d.ts +3 -2
  74. package/lib/deltaManager.d.ts.map +1 -1
  75. package/lib/deltaManager.js +4 -2
  76. package/lib/deltaManager.js.map +1 -1
  77. package/lib/deltaManagerProxy.d.ts +10 -22
  78. package/lib/deltaManagerProxy.d.ts.map +1 -1
  79. package/lib/deltaManagerProxy.js +14 -50
  80. package/lib/deltaManagerProxy.js.map +1 -1
  81. package/lib/index.d.ts +3 -2
  82. package/lib/index.d.ts.map +1 -1
  83. package/lib/index.js +2 -2
  84. package/lib/index.js.map +1 -1
  85. package/lib/loader.d.ts +10 -1
  86. package/lib/loader.d.ts.map +1 -1
  87. package/lib/loader.js +21 -16
  88. package/lib/loader.js.map +1 -1
  89. package/lib/packageVersion.d.ts +1 -1
  90. package/lib/packageVersion.js +1 -1
  91. package/lib/packageVersion.js.map +1 -1
  92. package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
  93. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  94. package/lib/protocolTreeDocumentStorageService.js +7 -4
  95. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  96. package/lib/utils.d.ts.map +1 -1
  97. package/lib/utils.js +2 -1
  98. package/lib/utils.js.map +1 -1
  99. package/package.json +64 -56
  100. package/src/connectionManager.ts +48 -17
  101. package/src/connectionStateHandler.ts +17 -5
  102. package/src/container.ts +224 -116
  103. package/src/containerContext.ts +74 -11
  104. package/src/containerStorageAdapter.ts +113 -9
  105. package/src/contracts.ts +2 -2
  106. package/src/deltaManager.ts +9 -4
  107. package/src/deltaManagerProxy.ts +18 -73
  108. package/src/index.ts +2 -3
  109. package/src/loader.ts +28 -26
  110. package/src/packageVersion.ts +1 -1
  111. package/src/protocolTreeDocumentStorageService.ts +6 -3
  112. package/src/utils.ts +7 -4
package/dist/container.js CHANGED
@@ -127,9 +127,15 @@ async function ReportIfTooLong(logger, eventName, action) {
127
127
  }
128
128
  exports.ReportIfTooLong = ReportIfTooLong;
129
129
  const summarizerClientType = "summarizer";
130
+ /**
131
+ * @deprecated - In the next release Container will no longer be exported, IContainer should be used in its place.
132
+ */
130
133
  class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
134
+ /**
135
+ * @internal
136
+ */
131
137
  constructor(loader, config, protocolHandlerBuilder) {
132
- var _a, _b;
138
+ var _a, _b, _c;
133
139
  super((name, error) => {
134
140
  this.mc.logger.sendErrorEvent({
135
141
  eventName: "ContainerEventHandlerException",
@@ -165,6 +171,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
165
171
  this.messageCountAfterDisconnection = 0;
166
172
  this.attachStarted = false;
167
173
  this._dirtyContainer = false;
174
+ this.savedOps = [];
168
175
  this.setAutoReconnectTime = common_utils_1.performance.now();
169
176
  this._disposed = false;
170
177
  this.clientDetailsOverride = config.clientDetailsOverride;
@@ -203,15 +210,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
203
210
  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; },
204
211
  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; },
205
212
  dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
213
+ 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; },
206
214
  connectionStateDuration: () => common_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
207
215
  },
208
216
  });
209
217
  // Prefix all events in this file with container-loader
210
218
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.subLogger, "Container"));
211
- const summarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _a !== void 0 ? _a : this.loader.services.options.summarizeProtocolTree;
212
- this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
219
+ this.options = Object.assign({}, this.loader.services.options);
213
220
  this._deltaManager = this.createDeltaManager();
214
- this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
215
221
  this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
216
222
  logger: this.mc.logger,
217
223
  connectionStateChanged: (value, oldState, reason) => {
@@ -250,13 +256,20 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
250
256
  this.connect();
251
257
  }
252
258
  },
253
- }, this.deltaManager, this._clientId);
259
+ }, this.deltaManager, (_a = config.serializedContainerState) === null || _a === void 0 ? void 0 : _a.clientId);
254
260
  this.on(savedContainerEvent, () => {
255
261
  this.connectionStateHandler.containerSaved();
256
262
  });
257
- this.storageService = new containerStorageAdapter_1.ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, this.options.summarizeProtocolTree === true
258
- ? () => this.captureProtocolSummary()
259
- : undefined);
263
+ // We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
264
+ // non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
265
+ // using this callback and fix them up.
266
+ const addProtocolSummaryIfMissing = (summaryTree) => (0, driver_utils_1.isCombinedAppAndProtocolSummary)(summaryTree) === true
267
+ ? summaryTree
268
+ : (0, driver_utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
269
+ // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
270
+ // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
271
+ const forceEnableSummarizeProtocolTree = (_b = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _b !== void 0 ? _b : this.loader.services.options.summarizeProtocolTree;
272
+ this.storageAdapter = new containerStorageAdapter_1.ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, (_c = config.serializedContainerState) === null || _c === void 0 ? void 0 : _c.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
260
273
  const isDomAvailable = typeof document === "object" &&
261
274
  document !== null &&
262
275
  typeof document.addEventListener === "function" &&
@@ -277,45 +290,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
277
290
  };
278
291
  document.addEventListener("visibilitychange", this.visibilityEventHandler);
279
292
  }
280
- // We observed that most users of platform do not check Container.connected event on load, causing bugs.
281
- // As such, we are raising events when new listener pops up.
282
- // Note that we can raise both "disconnected" & "connect" events at the same time,
283
- // if we are in connecting stage.
284
- this.on("newListener", (event, listener) => {
285
- // Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
286
- Promise.resolve()
287
- .then(() => {
288
- switch (event) {
289
- case dirtyContainerEvent:
290
- if (this._dirtyContainer) {
291
- listener();
292
- }
293
- break;
294
- case savedContainerEvent:
295
- if (!this._dirtyContainer) {
296
- listener();
297
- }
298
- break;
299
- case telemetry_utils_1.connectedEventName:
300
- if (this.connected) {
301
- listener(this.clientId);
302
- }
303
- break;
304
- case telemetry_utils_1.disconnectedEventName:
305
- if (!this.connected) {
306
- listener();
307
- }
308
- break;
309
- default:
310
- }
311
- })
312
- .catch((error) => {
313
- this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
314
- });
315
- });
316
293
  }
317
294
  /**
318
295
  * Load an existing container.
296
+ * @internal
319
297
  */
320
298
  static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
321
299
  const container = new Container(loader, {
@@ -393,7 +371,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
393
371
  this._lifecycleState === "disposed");
394
372
  }
395
373
  get storage() {
396
- return this.storageService;
374
+ return this.storageAdapter;
397
375
  }
398
376
  get context() {
399
377
  if (this._context === undefined) {
@@ -464,6 +442,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
464
442
  get clientDetails() {
465
443
  return this._deltaManager.clientDetails;
466
444
  }
445
+ get offlineLoadEnabled() {
446
+ var _a;
447
+ // summarizer will not have any pending state we want to save
448
+ return (((_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : false) &&
449
+ this.clientDetails.capabilities.interactive);
450
+ }
467
451
  /**
468
452
  * Get the code details that are currently specified for the container.
469
453
  * @returns The current code details if any are specified, undefined if none are specified.
@@ -506,6 +490,38 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
506
490
  get codeLoader() {
507
491
  return this.loader.services.codeLoader;
508
492
  }
493
+ /**
494
+ * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
495
+ */
496
+ async getEntryPoint() {
497
+ var _a, _b;
498
+ // Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
499
+ // allow it since they mean a kind of read-only state for the Container.
500
+ // Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
501
+ if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
502
+ throw new container_utils_1.UsageError("The container is disposing or disposed");
503
+ }
504
+ while (this._context === undefined) {
505
+ await new Promise((resolve, reject) => {
506
+ const contextChangedHandler = () => {
507
+ resolve();
508
+ this.off("disposed", disposedHandler);
509
+ };
510
+ const disposedHandler = (error) => {
511
+ reject(error !== null && error !== void 0 ? error : "The Container is disposed");
512
+ this.off("contextChanged", contextChangedHandler);
513
+ };
514
+ this.once("contextChanged", contextChangedHandler);
515
+ this.once("disposed", disposedHandler);
516
+ });
517
+ // The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
518
+ // should have set this._context; making sure.
519
+ (0, common_utils_1.assert)(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
520
+ }
521
+ // Disable lint rule for the sake of more complete stack traces
522
+ // eslint-disable-next-line no-return-await
523
+ return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
524
+ }
509
525
  /**
510
526
  * Retrieves the quorum associated with the document
511
527
  */
@@ -536,15 +552,19 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
536
552
  try {
537
553
  // Raise event first, to ensure we capture _lifecycleState before transition.
538
554
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
555
+ // Log generic events instead of error events if container is in loading state, as most errors are not really FF errors
556
+ // which can pollute telemetry for real bugs
539
557
  this.mc.logger.sendTelemetryEvent({
540
558
  eventName: "ContainerClose",
541
- category: error === undefined ? "generic" : "error",
559
+ category: this._lifecycleState !== "loading" && error !== undefined
560
+ ? "error"
561
+ : "generic",
542
562
  }, error);
543
563
  this._lifecycleState = "closing";
544
564
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
545
565
  this.connectionStateHandler.dispose();
546
566
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
547
- this.storageService.dispose();
567
+ this.storageAdapter.dispose();
548
568
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
549
569
  // about file, like file being overwritten in storage, but client having stale local cache.
550
570
  // Driver need to ensure all caches are cleared on critical errors
@@ -573,7 +593,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
573
593
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
574
594
  this.mc.logger.sendTelemetryEvent({
575
595
  eventName: "ContainerDispose",
576
- category: error === undefined ? "generic" : "error",
596
+ category: "generic",
577
597
  }, error);
578
598
  // ! Progressing from "closed" to "disposing" is not allowed
579
599
  if (this._lifecycleState !== "closed") {
@@ -582,7 +602,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
582
602
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
583
603
  this.connectionStateHandler.dispose();
584
604
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
585
- this.storageService.dispose();
605
+ this.storageAdapter.dispose();
586
606
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
587
607
  // about file, like file being overwritten in storage, but client having stale local cache.
588
608
  // Driver need to ensure all caches are cleared on critical errors
@@ -605,14 +625,21 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
605
625
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
606
626
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
607
627
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
628
+ if (!this.offlineLoadEnabled) {
629
+ throw new container_utils_1.UsageError("Can't get pending local state unless offline load is enabled");
630
+ }
608
631
  (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
609
632
  (0, common_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
610
633
  (0, common_utils_1.assert)(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
611
634
  (0, common_utils_1.assert)(this._protocolHandler.attributes.term !== undefined, 0x37e /* Must have a valid protocol handler instance */);
635
+ (0, common_utils_1.assert)(!!this.baseSnapshot, "no base snapshot");
636
+ (0, common_utils_1.assert)(!!this.baseSnapshotBlobs, "no snapshot blobs");
612
637
  const pendingState = {
613
638
  pendingRuntimeState: this.context.getPendingLocalState(),
639
+ baseSnapshot: this.baseSnapshot,
640
+ snapshotBlobs: this.baseSnapshotBlobs,
641
+ savedOps: this.savedOps,
614
642
  url: this.resolvedUrl.url,
615
- protocol: this.protocolHandler.getProtocolState(),
616
643
  term: this._protocolHandler.attributes.term,
617
644
  clientId: this.clientId,
618
645
  };
@@ -665,7 +692,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
665
692
  // starting to attach the container to storage.
666
693
  // Also, this should only be fired in detached container.
667
694
  this._attachState = container_definitions_1.AttachState.Attaching;
668
- this.context.notifyAttaching((0, utils_1.getSnapshotTreeFromSerializedContainer)(summary));
695
+ this.emit("attaching");
696
+ if (this.offlineLoadEnabled) {
697
+ const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
698
+ this.baseSnapshot = snapshot;
699
+ this.baseSnapshotBlobs =
700
+ (0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
701
+ }
669
702
  }
670
703
  // Actually go and create the resolved document
671
704
  const createNewResolvedUrl = await this.urlResolver.resolve(request);
@@ -679,7 +712,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
679
712
  const resolvedUrl = this.service.resolvedUrl;
680
713
  (0, driver_utils_1.ensureFluidResolvedUrl)(resolvedUrl);
681
714
  this._resolvedUrl = resolvedUrl;
682
- await this.storageService.connectToService(this.service);
715
+ await this.storageAdapter.connectToService(this.service);
683
716
  if (hasAttachmentBlobs) {
684
717
  // upload blobs to storage
685
718
  (0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
@@ -693,7 +726,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
693
726
  .filter((id) => !redirectTable.has(id));
694
727
  for (const id of newIds) {
695
728
  const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
696
- const response = await this.storageService.createBlob(blob);
729
+ const response = await this.storageAdapter.createBlob(blob);
697
730
  redirectTable.set(id, response.id);
698
731
  }
699
732
  }
@@ -702,8 +735,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
702
735
  const protocolSummary = this.captureProtocolSummary();
703
736
  summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
704
737
  this._attachState = container_definitions_1.AttachState.Attaching;
705
- this.context.notifyAttaching((0, utils_1.getSnapshotTreeFromSerializedContainer)(summary));
706
- await this.storageService.uploadSummaryWithContext(summary, {
738
+ this.emit("attaching");
739
+ if (this.offlineLoadEnabled) {
740
+ const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
741
+ this.baseSnapshot = snapshot;
742
+ this.baseSnapshotBlobs =
743
+ (0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
744
+ }
745
+ await this.storageAdapter.uploadSummaryWithContext(summary, {
707
746
  referenceSequenceNumber: 0,
708
747
  ackHandle: undefined,
709
748
  proposalHandle: undefined,
@@ -838,7 +877,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
838
877
  (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
839
878
  }
840
879
  async getVersion(version) {
841
- const versions = await this.storageService.getVersions(version, 1);
880
+ const versions = await this.storageAdapter.getVersions(version, 1);
842
881
  return versions[0];
843
882
  }
844
883
  recordConnectStartTime() {
@@ -860,6 +899,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
860
899
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
861
900
  */
862
901
  async load(specifiedVersion, loadMode, pendingLocalState) {
902
+ var _a;
863
903
  if (this._resolvedUrl === undefined) {
864
904
  throw new Error("Attempting to load without a resolved url");
865
905
  }
@@ -880,15 +920,15 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
880
920
  };
881
921
  // Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
882
922
  // DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
883
- if (loadMode.deltaConnection === undefined) {
923
+ if (loadMode.deltaConnection === undefined && !pendingLocalState) {
884
924
  this.connectToDeltaStream(connectionArgs);
885
925
  }
886
926
  if (!pendingLocalState) {
887
- await this.storageService.connectToService(this.service);
927
+ await this.storageAdapter.connectToService(this.service);
888
928
  }
889
929
  else {
890
930
  // if we have pendingLocalState we can load without storage; don't wait for connection
891
- this.storageService.connectToService(this.service).catch((error) => {
931
+ this.storageAdapter.connectToService(this.service).catch((error) => {
892
932
  var _a;
893
933
  this.close(error);
894
934
  (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
@@ -898,15 +938,23 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
898
938
  // Fetch specified snapshot.
899
939
  const { snapshot, versionId } = pendingLocalState === undefined
900
940
  ? await this.fetchSnapshotTree(specifiedVersion)
901
- : { snapshot: undefined, versionId: undefined };
902
- (0, common_utils_1.assert)(snapshot !== undefined || pendingLocalState !== undefined, 0x237 /* "Snapshot should exist" */);
903
- const attributes = pendingLocalState === undefined
904
- ? await this.getDocumentAttributes(this.storageService, snapshot)
905
- : {
906
- sequenceNumber: pendingLocalState.protocol.sequenceNumber,
907
- minimumSequenceNumber: pendingLocalState.protocol.minimumSequenceNumber,
908
- term: pendingLocalState.term,
909
- };
941
+ : { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
942
+ if (pendingLocalState) {
943
+ this.baseSnapshot = pendingLocalState.baseSnapshot;
944
+ this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
945
+ }
946
+ else {
947
+ (0, common_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
948
+ if (this.offlineLoadEnabled) {
949
+ this.baseSnapshot = snapshot;
950
+ // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
951
+ this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storage);
952
+ }
953
+ }
954
+ const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
955
+ // If we saved ops, we will replay them and don't need DeltaManager to fetch them
956
+ const sequenceNumber = (_a = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.savedOps[pendingLocalState.savedOps.length - 1]) === null || _a === void 0 ? void 0 : _a.sequenceNumber;
957
+ const dmAttributes = sequenceNumber !== undefined ? Object.assign(Object.assign({}, attributes), { sequenceNumber }) : attributes;
910
958
  let opsBeforeReturnP;
911
959
  // Attach op handlers to finish initialization and be able to start processing ops
912
960
  // Kick off any ops fetching if required.
@@ -914,32 +962,35 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
914
962
  case undefined:
915
963
  // Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
916
964
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
917
- this.attachDeltaManagerOpHandler(attributes, loadMode.deltaConnection !== "none" ? "all" : "none");
965
+ this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
918
966
  break;
919
967
  case "cached":
920
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "cached");
968
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
921
969
  break;
922
970
  case "all":
923
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "all");
971
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
924
972
  break;
925
973
  default:
926
974
  (0, common_utils_1.unreachableCase)(loadMode.opsBeforeReturn);
927
975
  }
928
976
  // ...load in the existing quorum
929
977
  // Initialize the protocol handler
930
- if (pendingLocalState === undefined) {
931
- await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
932
- }
933
- else {
934
- this.initializeProtocolState(attributes, {
935
- members: pendingLocalState.protocol.members,
936
- proposals: pendingLocalState.protocol.proposals,
937
- values: pendingLocalState.protocol.values,
938
- });
939
- }
978
+ await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
940
979
  const codeDetails = this.getCodeDetailsFromQuorum();
941
980
  await this.instantiateContext(true, // existing
942
981
  codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
982
+ // replay saved ops
983
+ if (pendingLocalState) {
984
+ for (const message of pendingLocalState.savedOps) {
985
+ this.processRemoteMessage(message);
986
+ // allow runtime to apply stashed ops at this op's sequence number
987
+ await this.context.notifyOpReplay(message);
988
+ }
989
+ pendingLocalState.savedOps = [];
990
+ // now set clientId to stashed clientId so live ops are correctly processed as local
991
+ (0, common_utils_1.assert)(this.clientId === undefined, "Unexpected clientId when setting stashed clientId");
992
+ this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
993
+ }
943
994
  // We might have hit some failure that did not manifest itself in exception in this flow,
944
995
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
945
996
  if (!this.closed) {
@@ -952,6 +1003,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
952
1003
  }
953
1004
  switch (loadMode.deltaConnection) {
954
1005
  case undefined:
1006
+ if (pendingLocalState) {
1007
+ // connect to delta stream now since we did not before
1008
+ this.connectToDeltaStream(connectionArgs);
1009
+ }
1010
+ // intentional fallthrough
955
1011
  case "delayed":
956
1012
  (0, common_utils_1.assert)(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
957
1013
  this.inboundQueuePausedFromInit = false;
@@ -1006,12 +1062,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1006
1062
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
1007
1063
  }
1008
1064
  const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
1009
- this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
1010
- const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
1065
+ this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
1066
+ const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
1011
1067
  await this.attachDeltaManagerOpHandler(attributes);
1012
1068
  // Initialize the protocol handler
1013
1069
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
1014
- const qValues = await (0, driver_utils_1.readAndParse)(this.storageService, baseTree.blobs.quorumValues);
1070
+ const qValues = await (0, driver_utils_1.readAndParse)(this.storageAdapter, baseTree.blobs.quorumValues);
1015
1071
  const codeDetails = (0, quorum_1.getCodeDetailsFromQuorumValues)(qValues);
1016
1072
  this.initializeProtocolState(attributes, {
1017
1073
  members: [],
@@ -1288,16 +1344,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1288
1344
  }
1289
1345
  }
1290
1346
  /** @returns clientSequenceNumber of last message in a batch */
1291
- submitBatch(batch) {
1347
+ submitBatch(batch, referenceSequenceNumber) {
1292
1348
  let clientSequenceNumber = -1;
1293
1349
  for (const message of batch) {
1294
1350
  clientSequenceNumber = this.submitMessage(protocol_definitions_1.MessageType.Operation, message.contents, true, // batch
1295
- message.metadata, message.compression);
1351
+ message.metadata, message.compression, referenceSequenceNumber);
1296
1352
  }
1297
1353
  this._deltaManager.flush();
1298
1354
  return clientSequenceNumber;
1299
1355
  }
1300
- submitSummaryMessage(summary) {
1356
+ submitSummaryMessage(summary, referenceSequenceNumber) {
1301
1357
  // github #6451: this is only needed for staging so the server
1302
1358
  // know when the protocol tree is included
1303
1359
  // this can be removed once all clients send
@@ -1305,10 +1361,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1305
1361
  if (summary.details === undefined) {
1306
1362
  summary.details = {};
1307
1363
  }
1308
- summary.details.includesProtocolTree = this.options.summarizeProtocolTree === true;
1309
- return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1364
+ summary.details.includesProtocolTree = this.storageAdapter.summarizeProtocolTree;
1365
+ return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
1310
1366
  }
1311
- submitMessage(type, contents, batch, metadata, compression) {
1367
+ submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
1312
1368
  var _a;
1313
1369
  if (this.connectionState !== connectionState_1.ConnectionState.Connected) {
1314
1370
  this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
@@ -1316,9 +1372,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1316
1372
  }
1317
1373
  this.messageCountAfterDisconnection += 1;
1318
1374
  (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1319
- return this._deltaManager.submit(type, contents, batch, metadata, compression);
1375
+ return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1320
1376
  }
1321
1377
  processRemoteMessage(message) {
1378
+ if (this.offlineLoadEnabled) {
1379
+ this.savedOps.push(message);
1380
+ }
1322
1381
  const local = this.clientId === message.clientId;
1323
1382
  // Allow the protocol handler to process the message
1324
1383
  const result = this.protocolHandler.processMessage(message, local);
@@ -1370,7 +1429,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1370
1429
  });
1371
1430
  }
1372
1431
  this._loadedFromVersion = version;
1373
- const snapshot = (_a = (await this.storageService.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
1432
+ const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
1374
1433
  if (snapshot === undefined && version !== undefined) {
1375
1434
  this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
1376
1435
  }
@@ -1389,7 +1448,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1389
1448
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1390
1449
  // are set. Global requests will still go directly to the loader
1391
1450
  const loader = new loader_1.RelativeLoader(this, this.loader);
1392
- this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp) => this.submitSummaryMessage(summaryOp), (batch) => this.submitBatch(batch), (message) => this.submitSignal(message), (error) => { var _a; return (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error); }, (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1451
+ this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.QuorumProxy(this.protocolHandler.quorum), 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) => { var _a; return (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error); }, (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1393
1452
  this.emit("contextChanged", codeDetails);
1394
1453
  }
1395
1454
  updateDirtyContainerState(dirty) {