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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +45 -4
  3. package/closeAndGetPendingLocalState.md +51 -0
  4. package/dist/connectionManager.d.ts +2 -1
  5. package/dist/connectionManager.d.ts.map +1 -1
  6. package/dist/connectionManager.js +59 -17
  7. package/dist/connectionManager.js.map +1 -1
  8. package/dist/connectionStateHandler.d.ts +4 -4
  9. package/dist/connectionStateHandler.d.ts.map +1 -1
  10. package/dist/connectionStateHandler.js +7 -0
  11. package/dist/connectionStateHandler.js.map +1 -1
  12. package/dist/container.d.ts +44 -4
  13. package/dist/container.d.ts.map +1 -1
  14. package/dist/container.js +160 -105
  15. package/dist/container.js.map +1 -1
  16. package/dist/containerContext.d.ts +18 -8
  17. package/dist/containerContext.d.ts.map +1 -1
  18. package/dist/containerContext.js +47 -4
  19. package/dist/containerContext.js.map +1 -1
  20. package/dist/containerStorageAdapter.d.ts +41 -2
  21. package/dist/containerStorageAdapter.d.ts.map +1 -1
  22. package/dist/containerStorageAdapter.js +87 -11
  23. package/dist/containerStorageAdapter.js.map +1 -1
  24. package/dist/contracts.d.ts +2 -2
  25. package/dist/contracts.d.ts.map +1 -1
  26. package/dist/contracts.js.map +1 -1
  27. package/dist/deltaManager.d.ts +4 -5
  28. package/dist/deltaManager.d.ts.map +1 -1
  29. package/dist/deltaManager.js +7 -10
  30. package/dist/deltaManager.js.map +1 -1
  31. package/dist/deltaManagerProxy.d.ts +10 -22
  32. package/dist/deltaManagerProxy.d.ts.map +1 -1
  33. package/dist/deltaManagerProxy.js +14 -50
  34. package/dist/deltaManagerProxy.js.map +1 -1
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/loader.d.ts +10 -1
  40. package/dist/loader.d.ts.map +1 -1
  41. package/dist/loader.js +25 -16
  42. package/dist/loader.js.map +1 -1
  43. package/dist/packageVersion.d.ts +1 -1
  44. package/dist/packageVersion.js +1 -1
  45. package/dist/packageVersion.js.map +1 -1
  46. package/dist/protocol.d.ts +1 -0
  47. package/dist/protocol.d.ts.map +1 -1
  48. package/dist/protocol.js +4 -2
  49. package/dist/protocol.js.map +1 -1
  50. package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
  51. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  52. package/dist/protocolTreeDocumentStorageService.js +7 -4
  53. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  54. package/dist/utils.d.ts.map +1 -1
  55. package/dist/utils.js +2 -1
  56. package/dist/utils.js.map +1 -1
  57. package/lib/connectionManager.d.ts +2 -1
  58. package/lib/connectionManager.d.ts.map +1 -1
  59. package/lib/connectionManager.js +60 -18
  60. package/lib/connectionManager.js.map +1 -1
  61. package/lib/connectionStateHandler.d.ts +4 -4
  62. package/lib/connectionStateHandler.d.ts.map +1 -1
  63. package/lib/connectionStateHandler.js +7 -0
  64. package/lib/connectionStateHandler.js.map +1 -1
  65. package/lib/container.d.ts +44 -4
  66. package/lib/container.d.ts.map +1 -1
  67. package/lib/container.js +164 -109
  68. package/lib/container.js.map +1 -1
  69. package/lib/containerContext.d.ts +18 -8
  70. package/lib/containerContext.d.ts.map +1 -1
  71. package/lib/containerContext.js +48 -5
  72. package/lib/containerContext.js.map +1 -1
  73. package/lib/containerStorageAdapter.d.ts +41 -2
  74. package/lib/containerStorageAdapter.d.ts.map +1 -1
  75. package/lib/containerStorageAdapter.js +85 -11
  76. package/lib/containerStorageAdapter.js.map +1 -1
  77. package/lib/contracts.d.ts +2 -2
  78. package/lib/contracts.d.ts.map +1 -1
  79. package/lib/contracts.js.map +1 -1
  80. package/lib/deltaManager.d.ts +4 -5
  81. package/lib/deltaManager.d.ts.map +1 -1
  82. package/lib/deltaManager.js +7 -10
  83. package/lib/deltaManager.js.map +1 -1
  84. package/lib/deltaManagerProxy.d.ts +10 -22
  85. package/lib/deltaManagerProxy.d.ts.map +1 -1
  86. package/lib/deltaManagerProxy.js +14 -50
  87. package/lib/deltaManagerProxy.js.map +1 -1
  88. package/lib/index.d.ts +3 -2
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +2 -2
  91. package/lib/index.js.map +1 -1
  92. package/lib/loader.d.ts +10 -1
  93. package/lib/loader.d.ts.map +1 -1
  94. package/lib/loader.js +24 -16
  95. package/lib/loader.js.map +1 -1
  96. package/lib/packageVersion.d.ts +1 -1
  97. package/lib/packageVersion.js +1 -1
  98. package/lib/packageVersion.js.map +1 -1
  99. package/lib/protocol.d.ts +1 -0
  100. package/lib/protocol.d.ts.map +1 -1
  101. package/lib/protocol.js +3 -1
  102. package/lib/protocol.js.map +1 -1
  103. package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
  104. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  105. package/lib/protocolTreeDocumentStorageService.js +7 -4
  106. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  107. package/lib/utils.d.ts.map +1 -1
  108. package/lib/utils.js +2 -1
  109. package/lib/utils.js.map +1 -1
  110. package/package.json +64 -56
  111. package/src/connectionManager.ts +65 -24
  112. package/src/connectionStateHandler.ts +17 -5
  113. package/src/container.ts +239 -137
  114. package/src/containerContext.ts +74 -11
  115. package/src/containerStorageAdapter.ts +113 -9
  116. package/src/contracts.ts +2 -2
  117. package/src/deltaManager.ts +12 -14
  118. package/src/deltaManagerProxy.ts +18 -73
  119. package/src/index.ts +3 -3
  120. package/src/loader.ts +31 -26
  121. package/src/packageVersion.ts +1 -1
  122. package/src/protocol.ts +4 -1
  123. package/src/protocolTreeDocumentStorageService.ts +6 -3
  124. package/src/utils.ts +7 -4
package/dist/container.js CHANGED
@@ -10,6 +10,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.Container = exports.ReportIfTooLong = exports.waitContainerToCatchUp = void 0;
11
11
  // eslint-disable-next-line import/no-internal-modules
12
12
  const merge_1 = __importDefault(require("lodash/merge"));
13
+ // eslint-disable-next-line import/no-internal-modules
14
+ const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
13
15
  const uuid_1 = require("uuid");
14
16
  const common_utils_1 = require("@fluidframework/common-utils");
15
17
  const container_definitions_1 = require("@fluidframework/container-definitions");
@@ -127,9 +129,15 @@ async function ReportIfTooLong(logger, eventName, action) {
127
129
  }
128
130
  exports.ReportIfTooLong = ReportIfTooLong;
129
131
  const summarizerClientType = "summarizer";
132
+ /**
133
+ * @deprecated - In the next release Container will no longer be exported, IContainer should be used in its place.
134
+ */
130
135
  class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
136
+ /**
137
+ * @internal
138
+ */
131
139
  constructor(loader, config, protocolHandlerBuilder) {
132
- var _a, _b;
140
+ var _a, _b, _c;
133
141
  super((name, error) => {
134
142
  this.mc.logger.sendErrorEvent({
135
143
  eventName: "ContainerEventHandlerException",
@@ -165,6 +173,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
165
173
  this.messageCountAfterDisconnection = 0;
166
174
  this.attachStarted = false;
167
175
  this._dirtyContainer = false;
176
+ this.savedOps = [];
168
177
  this.setAutoReconnectTime = common_utils_1.performance.now();
169
178
  this._disposed = false;
170
179
  this.clientDetailsOverride = config.clientDetailsOverride;
@@ -203,15 +212,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
203
212
  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
213
  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
214
  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; },
215
+ 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
216
  connectionStateDuration: () => common_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
207
217
  },
208
218
  });
209
219
  // Prefix all events in this file with container-loader
210
220
  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 });
221
+ this.options = (0, cloneDeep_1.default)(this.loader.services.options);
213
222
  this._deltaManager = this.createDeltaManager();
214
- this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
215
223
  this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
216
224
  logger: this.mc.logger,
217
225
  connectionStateChanged: (value, oldState, reason) => {
@@ -250,13 +258,20 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
250
258
  this.connect();
251
259
  }
252
260
  },
253
- }, this.deltaManager, this._clientId);
261
+ }, this.deltaManager, (_a = config.serializedContainerState) === null || _a === void 0 ? void 0 : _a.clientId);
254
262
  this.on(savedContainerEvent, () => {
255
263
  this.connectionStateHandler.containerSaved();
256
264
  });
257
- this.storageService = new containerStorageAdapter_1.ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, this.options.summarizeProtocolTree === true
258
- ? () => this.captureProtocolSummary()
259
- : undefined);
265
+ // We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
266
+ // non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
267
+ // using this callback and fix them up.
268
+ const addProtocolSummaryIfMissing = (summaryTree) => (0, driver_utils_1.isCombinedAppAndProtocolSummary)(summaryTree) === true
269
+ ? summaryTree
270
+ : (0, driver_utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
271
+ // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
272
+ // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
273
+ const forceEnableSummarizeProtocolTree = (_b = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _b !== void 0 ? _b : this.loader.services.options.summarizeProtocolTree;
274
+ 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
275
  const isDomAvailable = typeof document === "object" &&
261
276
  document !== null &&
262
277
  typeof document.addEventListener === "function" &&
@@ -277,45 +292,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
277
292
  };
278
293
  document.addEventListener("visibilitychange", this.visibilityEventHandler);
279
294
  }
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
295
  }
317
296
  /**
318
297
  * Load an existing container.
298
+ * @internal
319
299
  */
320
300
  static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
321
301
  const container = new Container(loader, {
@@ -393,7 +373,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
393
373
  this._lifecycleState === "disposed");
394
374
  }
395
375
  get storage() {
396
- return this.storageService;
376
+ return this.storageAdapter;
397
377
  }
398
378
  get context() {
399
379
  if (this._context === undefined) {
@@ -464,6 +444,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
464
444
  get clientDetails() {
465
445
  return this._deltaManager.clientDetails;
466
446
  }
447
+ get offlineLoadEnabled() {
448
+ var _a;
449
+ // summarizer will not have any pending state we want to save
450
+ return (((_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : false) &&
451
+ this.clientDetails.capabilities.interactive);
452
+ }
467
453
  /**
468
454
  * Get the code details that are currently specified for the container.
469
455
  * @returns The current code details if any are specified, undefined if none are specified.
@@ -506,6 +492,38 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
506
492
  get codeLoader() {
507
493
  return this.loader.services.codeLoader;
508
494
  }
495
+ /**
496
+ * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
497
+ */
498
+ async getEntryPoint() {
499
+ var _a, _b;
500
+ // Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
501
+ // allow it since they mean a kind of read-only state for the Container.
502
+ // Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
503
+ if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
504
+ throw new container_utils_1.UsageError("The container is disposing or disposed");
505
+ }
506
+ while (this._context === undefined) {
507
+ await new Promise((resolve, reject) => {
508
+ const contextChangedHandler = () => {
509
+ resolve();
510
+ this.off("disposed", disposedHandler);
511
+ };
512
+ const disposedHandler = (error) => {
513
+ reject(error !== null && error !== void 0 ? error : "The Container is disposed");
514
+ this.off("contextChanged", contextChangedHandler);
515
+ };
516
+ this.once("contextChanged", contextChangedHandler);
517
+ this.once("disposed", disposedHandler);
518
+ });
519
+ // The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
520
+ // should have set this._context; making sure.
521
+ (0, common_utils_1.assert)(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
522
+ }
523
+ // Disable lint rule for the sake of more complete stack traces
524
+ // eslint-disable-next-line no-return-await
525
+ return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
526
+ }
509
527
  /**
510
528
  * Retrieves the quorum associated with the document
511
529
  */
@@ -536,15 +554,19 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
536
554
  try {
537
555
  // Raise event first, to ensure we capture _lifecycleState before transition.
538
556
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
557
+ // Log generic events instead of error events if container is in loading state, as most errors are not really FF errors
558
+ // which can pollute telemetry for real bugs
539
559
  this.mc.logger.sendTelemetryEvent({
540
560
  eventName: "ContainerClose",
541
- category: error === undefined ? "generic" : "error",
561
+ category: this._lifecycleState !== "loading" && error !== undefined
562
+ ? "error"
563
+ : "generic",
542
564
  }, error);
543
565
  this._lifecycleState = "closing";
544
566
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
545
567
  this.connectionStateHandler.dispose();
546
568
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
547
- this.storageService.dispose();
569
+ this.storageAdapter.dispose();
548
570
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
549
571
  // about file, like file being overwritten in storage, but client having stale local cache.
550
572
  // Driver need to ensure all caches are cleared on critical errors
@@ -573,7 +595,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
573
595
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
574
596
  this.mc.logger.sendTelemetryEvent({
575
597
  eventName: "ContainerDispose",
576
- category: error === undefined ? "generic" : "error",
598
+ category: "generic",
577
599
  }, error);
578
600
  // ! Progressing from "closed" to "disposing" is not allowed
579
601
  if (this._lifecycleState !== "closed") {
@@ -582,7 +604,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
582
604
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
583
605
  this.connectionStateHandler.dispose();
584
606
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
585
- this.storageService.dispose();
607
+ this.storageAdapter.dispose();
586
608
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
587
609
  // about file, like file being overwritten in storage, but client having stale local cache.
588
610
  // Driver need to ensure all caches are cleared on critical errors
@@ -605,15 +627,21 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
605
627
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
606
628
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
607
629
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
630
+ if (!this.offlineLoadEnabled) {
631
+ throw new container_utils_1.UsageError("Can't get pending local state unless offline load is enabled");
632
+ }
608
633
  (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
609
634
  (0, common_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
610
635
  (0, common_utils_1.assert)(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
611
- (0, common_utils_1.assert)(this._protocolHandler.attributes.term !== undefined, 0x37e /* Must have a valid protocol handler instance */);
636
+ (0, common_utils_1.assert)(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
637
+ (0, common_utils_1.assert)(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
612
638
  const pendingState = {
613
639
  pendingRuntimeState: this.context.getPendingLocalState(),
640
+ baseSnapshot: this.baseSnapshot,
641
+ snapshotBlobs: this.baseSnapshotBlobs,
642
+ savedOps: this.savedOps,
614
643
  url: this.resolvedUrl.url,
615
- protocol: this.protocolHandler.getProtocolState(),
616
- term: this._protocolHandler.attributes.term,
644
+ term: protocol_1.OnlyValidTermValue,
617
645
  clientId: this.clientId,
618
646
  };
619
647
  this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
@@ -665,7 +693,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
665
693
  // starting to attach the container to storage.
666
694
  // Also, this should only be fired in detached container.
667
695
  this._attachState = container_definitions_1.AttachState.Attaching;
668
- this.context.notifyAttaching((0, utils_1.getSnapshotTreeFromSerializedContainer)(summary));
696
+ this.emit("attaching");
697
+ if (this.offlineLoadEnabled) {
698
+ const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
699
+ this.baseSnapshot = snapshot;
700
+ this.baseSnapshotBlobs =
701
+ (0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
702
+ }
669
703
  }
670
704
  // Actually go and create the resolved document
671
705
  const createNewResolvedUrl = await this.urlResolver.resolve(request);
@@ -679,7 +713,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
679
713
  const resolvedUrl = this.service.resolvedUrl;
680
714
  (0, driver_utils_1.ensureFluidResolvedUrl)(resolvedUrl);
681
715
  this._resolvedUrl = resolvedUrl;
682
- await this.storageService.connectToService(this.service);
716
+ await this.storageAdapter.connectToService(this.service);
683
717
  if (hasAttachmentBlobs) {
684
718
  // upload blobs to storage
685
719
  (0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
@@ -693,7 +727,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
693
727
  .filter((id) => !redirectTable.has(id));
694
728
  for (const id of newIds) {
695
729
  const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
696
- const response = await this.storageService.createBlob(blob);
730
+ const response = await this.storageAdapter.createBlob(blob);
697
731
  redirectTable.set(id, response.id);
698
732
  }
699
733
  }
@@ -702,8 +736,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
702
736
  const protocolSummary = this.captureProtocolSummary();
703
737
  summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
704
738
  this._attachState = container_definitions_1.AttachState.Attaching;
705
- this.context.notifyAttaching((0, utils_1.getSnapshotTreeFromSerializedContainer)(summary));
706
- await this.storageService.uploadSummaryWithContext(summary, {
739
+ this.emit("attaching");
740
+ if (this.offlineLoadEnabled) {
741
+ const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
742
+ this.baseSnapshot = snapshot;
743
+ this.baseSnapshotBlobs =
744
+ (0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
745
+ }
746
+ await this.storageAdapter.uploadSummaryWithContext(summary, {
707
747
  referenceSequenceNumber: 0,
708
748
  ackHandle: undefined,
709
749
  proposalHandle: undefined,
@@ -838,7 +878,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
838
878
  (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
839
879
  }
840
880
  async getVersion(version) {
841
- const versions = await this.storageService.getVersions(version, 1);
881
+ const versions = await this.storageAdapter.getVersions(version, 1);
842
882
  return versions[0];
843
883
  }
844
884
  recordConnectStartTime() {
@@ -860,6 +900,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
860
900
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
861
901
  */
862
902
  async load(specifiedVersion, loadMode, pendingLocalState) {
903
+ var _a;
863
904
  if (this._resolvedUrl === undefined) {
864
905
  throw new Error("Attempting to load without a resolved url");
865
906
  }
@@ -880,15 +921,15 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
880
921
  };
881
922
  // Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
882
923
  // DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
883
- if (loadMode.deltaConnection === undefined) {
924
+ if (loadMode.deltaConnection === undefined && !pendingLocalState) {
884
925
  this.connectToDeltaStream(connectionArgs);
885
926
  }
886
927
  if (!pendingLocalState) {
887
- await this.storageService.connectToService(this.service);
928
+ await this.storageAdapter.connectToService(this.service);
888
929
  }
889
930
  else {
890
931
  // if we have pendingLocalState we can load without storage; don't wait for connection
891
- this.storageService.connectToService(this.service).catch((error) => {
932
+ this.storageAdapter.connectToService(this.service).catch((error) => {
892
933
  var _a;
893
934
  this.close(error);
894
935
  (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
@@ -898,15 +939,23 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
898
939
  // Fetch specified snapshot.
899
940
  const { snapshot, versionId } = pendingLocalState === undefined
900
941
  ? 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
- };
942
+ : { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
943
+ if (pendingLocalState) {
944
+ this.baseSnapshot = pendingLocalState.baseSnapshot;
945
+ this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
946
+ }
947
+ else {
948
+ (0, common_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
949
+ if (this.offlineLoadEnabled) {
950
+ this.baseSnapshot = snapshot;
951
+ // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
952
+ this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storage);
953
+ }
954
+ }
955
+ const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
956
+ // If we saved ops, we will replay them and don't need DeltaManager to fetch them
957
+ const sequenceNumber = (_a = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.savedOps[pendingLocalState.savedOps.length - 1]) === null || _a === void 0 ? void 0 : _a.sequenceNumber;
958
+ const dmAttributes = sequenceNumber !== undefined ? Object.assign(Object.assign({}, attributes), { sequenceNumber }) : attributes;
910
959
  let opsBeforeReturnP;
911
960
  // Attach op handlers to finish initialization and be able to start processing ops
912
961
  // Kick off any ops fetching if required.
@@ -914,32 +963,35 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
914
963
  case undefined:
915
964
  // Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
916
965
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
917
- this.attachDeltaManagerOpHandler(attributes, loadMode.deltaConnection !== "none" ? "all" : "none");
966
+ this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
918
967
  break;
919
968
  case "cached":
920
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "cached");
969
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
921
970
  break;
922
971
  case "all":
923
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "all");
972
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
924
973
  break;
925
974
  default:
926
975
  (0, common_utils_1.unreachableCase)(loadMode.opsBeforeReturn);
927
976
  }
928
977
  // ...load in the existing quorum
929
978
  // 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
- }
979
+ await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
940
980
  const codeDetails = this.getCodeDetailsFromQuorum();
941
981
  await this.instantiateContext(true, // existing
942
982
  codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
983
+ // replay saved ops
984
+ if (pendingLocalState) {
985
+ for (const message of pendingLocalState.savedOps) {
986
+ this.processRemoteMessage(message);
987
+ // allow runtime to apply stashed ops at this op's sequence number
988
+ await this.context.notifyOpReplay(message);
989
+ }
990
+ pendingLocalState.savedOps = [];
991
+ // now set clientId to stashed clientId so live ops are correctly processed as local
992
+ (0, common_utils_1.assert)(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
993
+ this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
994
+ }
943
995
  // We might have hit some failure that did not manifest itself in exception in this flow,
944
996
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
945
997
  if (!this.closed) {
@@ -952,6 +1004,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
952
1004
  }
953
1005
  switch (loadMode.deltaConnection) {
954
1006
  case undefined:
1007
+ if (pendingLocalState) {
1008
+ // connect to delta stream now since we did not before
1009
+ this.connectToDeltaStream(connectionArgs);
1010
+ }
1011
+ // intentional fallthrough
955
1012
  case "delayed":
956
1013
  (0, common_utils_1.assert)(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
957
1014
  this.inboundQueuePausedFromInit = false;
@@ -984,7 +1041,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
984
1041
  async createDetached(source) {
985
1042
  const attributes = {
986
1043
  sequenceNumber: detachedContainerRefSeqNumber,
987
- term: 1,
1044
+ term: protocol_1.OnlyValidTermValue,
988
1045
  minimumSequenceNumber: 0,
989
1046
  };
990
1047
  await this.attachDeltaManagerOpHandler(attributes);
@@ -1006,12 +1063,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1006
1063
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
1007
1064
  }
1008
1065
  const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
1009
- this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
1010
- const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
1066
+ this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
1067
+ const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
1011
1068
  await this.attachDeltaManagerOpHandler(attributes);
1012
1069
  // Initialize the protocol handler
1013
1070
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
1014
- const qValues = await (0, driver_utils_1.readAndParse)(this.storageService, baseTree.blobs.quorumValues);
1071
+ const qValues = await (0, driver_utils_1.readAndParse)(this.storageAdapter, baseTree.blobs.quorumValues);
1015
1072
  const codeDetails = (0, quorum_1.getCodeDetailsFromQuorumValues)(qValues);
1016
1073
  this.initializeProtocolState(attributes, {
1017
1074
  members: [],
@@ -1027,7 +1084,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1027
1084
  return {
1028
1085
  minimumSequenceNumber: 0,
1029
1086
  sequenceNumber: 0,
1030
- term: 1,
1087
+ term: protocol_1.OnlyValidTermValue,
1031
1088
  };
1032
1089
  }
1033
1090
  // Backward compatibility: old docs would have ".attributes" instead of "attributes"
@@ -1035,10 +1092,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1035
1092
  ? tree.trees[".protocol"].blobs.attributes
1036
1093
  : tree.blobs[".attributes"];
1037
1094
  const attributes = await (0, driver_utils_1.readAndParse)(storage, attributesHash);
1038
- // Backward compatibility for older summaries with no term
1039
- if (attributes.term === undefined) {
1040
- attributes.term = 1;
1041
- }
1042
1095
  return attributes;
1043
1096
  }
1044
1097
  async initializeProtocolStateFromSnapshot(attributes, storage, snapshot) {
@@ -1158,7 +1211,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1158
1211
  }
1159
1212
  createDeltaManager() {
1160
1213
  const serviceProvider = () => this.service;
1161
- const deltaManager = new deltaManager_1.DeltaManager(serviceProvider, telemetry_utils_1.ChildLogger.create(this.subLogger, "DeltaManager"), () => this.activeConnection(), (props) => new connectionManager_1.ConnectionManager(serviceProvider, this.client, this._canReconnect, telemetry_utils_1.ChildLogger.create(this.subLogger, "ConnectionManager"), props));
1214
+ const deltaManager = new deltaManager_1.DeltaManager(serviceProvider, telemetry_utils_1.ChildLogger.create(this.subLogger, "DeltaManager"), () => this.activeConnection(), (props) => new connectionManager_1.ConnectionManager(serviceProvider, () => this.isDirty, this.client, this._canReconnect, telemetry_utils_1.ChildLogger.create(this.subLogger, "ConnectionManager"), props));
1162
1215
  // Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
1163
1216
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1164
1217
  deltaManager.inbound.pause();
@@ -1197,8 +1250,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1197
1250
  return deltaManager;
1198
1251
  }
1199
1252
  async attachDeltaManagerOpHandler(attributes, prefetchType) {
1200
- var _a;
1201
- return this._deltaManager.attachOpHandler(attributes.minimumSequenceNumber, attributes.sequenceNumber, (_a = attributes.term) !== null && _a !== void 0 ? _a : 1, {
1253
+ return this._deltaManager.attachOpHandler(attributes.minimumSequenceNumber, attributes.sequenceNumber, {
1202
1254
  process: (message) => this.processRemoteMessage(message),
1203
1255
  processSignal: (message) => {
1204
1256
  this.processSignal(message);
@@ -1261,7 +1313,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1261
1313
  this.messageCountAfterDisconnection = 0;
1262
1314
  }
1263
1315
  // Both protocol and context should not be undefined if we got so far.
1264
- this.setContextConnectedState(state, (_a = this._deltaManager.connectionManager.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1316
+ this.setContextConnectedState(state, (_a = this.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1265
1317
  this.protocolHandler.setConnectionState(state, this.clientId);
1266
1318
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason);
1267
1319
  if (logOpsOnReconnect) {
@@ -1288,16 +1340,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1288
1340
  }
1289
1341
  }
1290
1342
  /** @returns clientSequenceNumber of last message in a batch */
1291
- submitBatch(batch) {
1343
+ submitBatch(batch, referenceSequenceNumber) {
1292
1344
  let clientSequenceNumber = -1;
1293
1345
  for (const message of batch) {
1294
1346
  clientSequenceNumber = this.submitMessage(protocol_definitions_1.MessageType.Operation, message.contents, true, // batch
1295
- message.metadata, message.compression);
1347
+ message.metadata, message.compression, referenceSequenceNumber);
1296
1348
  }
1297
1349
  this._deltaManager.flush();
1298
1350
  return clientSequenceNumber;
1299
1351
  }
1300
- submitSummaryMessage(summary) {
1352
+ submitSummaryMessage(summary, referenceSequenceNumber) {
1301
1353
  // github #6451: this is only needed for staging so the server
1302
1354
  // know when the protocol tree is included
1303
1355
  // this can be removed once all clients send
@@ -1305,10 +1357,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1305
1357
  if (summary.details === undefined) {
1306
1358
  summary.details = {};
1307
1359
  }
1308
- summary.details.includesProtocolTree = this.options.summarizeProtocolTree === true;
1309
- return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1360
+ summary.details.includesProtocolTree = this.storageAdapter.summarizeProtocolTree;
1361
+ return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
1310
1362
  }
1311
- submitMessage(type, contents, batch, metadata, compression) {
1363
+ submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
1312
1364
  var _a;
1313
1365
  if (this.connectionState !== connectionState_1.ConnectionState.Connected) {
1314
1366
  this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
@@ -1316,9 +1368,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1316
1368
  }
1317
1369
  this.messageCountAfterDisconnection += 1;
1318
1370
  (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1319
- return this._deltaManager.submit(type, contents, batch, metadata, compression);
1371
+ return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1320
1372
  }
1321
1373
  processRemoteMessage(message) {
1374
+ if (this.offlineLoadEnabled) {
1375
+ this.savedOps.push(message);
1376
+ }
1322
1377
  const local = this.clientId === message.clientId;
1323
1378
  // Allow the protocol handler to process the message
1324
1379
  const result = this.protocolHandler.processMessage(message, local);
@@ -1370,7 +1425,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1370
1425
  });
1371
1426
  }
1372
1427
  this._loadedFromVersion = version;
1373
- const snapshot = (_a = (await this.storageService.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
1428
+ const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
1374
1429
  if (snapshot === undefined && version !== undefined) {
1375
1430
  this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
1376
1431
  }
@@ -1389,7 +1444,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1389
1444
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1390
1445
  // are set. Global requests will still go directly to the loader
1391
1446
  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);
1447
+ 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
1448
  this.emit("contextChanged", codeDetails);
1394
1449
  }
1395
1450
  updateDirtyContainerState(dirty) {