@fluidframework/container-loader 2.0.0-dev-rc.5.0.0.267932 → 2.0.0-dev-rc.5.0.0.270401

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 (165) hide show
  1. package/api-report/{container-loader.api.md → container-loader.alpha.api.md} +58 -15
  2. package/api-report/container-loader.beta.api.md +44 -0
  3. package/api-report/container-loader.public.api.md +44 -0
  4. package/dist/audience.js +2 -1
  5. package/dist/audience.js.map +1 -1
  6. package/dist/catchUpMonitor.js +11 -8
  7. package/dist/catchUpMonitor.js.map +1 -1
  8. package/dist/connectionManager.d.ts +2 -2
  9. package/dist/connectionManager.d.ts.map +1 -1
  10. package/dist/connectionManager.js +91 -63
  11. package/dist/connectionManager.js.map +1 -1
  12. package/dist/connectionStateHandler.js +35 -11
  13. package/dist/connectionStateHandler.js.map +1 -1
  14. package/dist/container.d.ts +2 -2
  15. package/dist/container.d.ts.map +1 -1
  16. package/dist/container.js +156 -123
  17. package/dist/container.js.map +1 -1
  18. package/dist/containerContext.d.ts +2 -2
  19. package/dist/containerContext.d.ts.map +1 -1
  20. package/dist/containerContext.js +34 -8
  21. package/dist/containerContext.js.map +1 -1
  22. package/dist/containerStorageAdapter.js +17 -9
  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/debugLogger.js +2 -0
  28. package/dist/debugLogger.js.map +1 -1
  29. package/dist/deltaManager.d.ts +2 -2
  30. package/dist/deltaManager.d.ts.map +1 -1
  31. package/dist/deltaManager.js +48 -35
  32. package/dist/deltaManager.js.map +1 -1
  33. package/dist/deltaQueue.js +14 -7
  34. package/dist/deltaQueue.js.map +1 -1
  35. package/dist/error.js +5 -4
  36. package/dist/error.js.map +1 -1
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/legacy.d.ts +5 -0
  41. package/dist/loader.js +5 -1
  42. package/dist/loader.js.map +1 -1
  43. package/dist/noopHeuristic.d.ts +1 -1
  44. package/dist/noopHeuristic.d.ts.map +1 -1
  45. package/dist/noopHeuristic.js +3 -1
  46. package/dist/noopHeuristic.js.map +1 -1
  47. package/dist/packageVersion.d.ts +1 -1
  48. package/dist/packageVersion.js +1 -1
  49. package/dist/packageVersion.js.map +1 -1
  50. package/dist/protocol/index.d.ts +7 -0
  51. package/dist/protocol/index.d.ts.map +1 -0
  52. package/dist/protocol/index.js +12 -0
  53. package/dist/protocol/index.js.map +1 -0
  54. package/dist/protocol/protocol.d.ts +52 -0
  55. package/dist/protocol/protocol.d.ts.map +1 -0
  56. package/dist/protocol/protocol.js +115 -0
  57. package/dist/protocol/protocol.js.map +1 -0
  58. package/dist/protocol/quorum.d.ts +185 -0
  59. package/dist/protocol/quorum.d.ts.map +1 -0
  60. package/dist/protocol/quorum.js +440 -0
  61. package/dist/protocol/quorum.js.map +1 -0
  62. package/dist/protocol.d.ts +2 -3
  63. package/dist/protocol.d.ts.map +1 -1
  64. package/dist/protocol.js +4 -2
  65. package/dist/protocol.js.map +1 -1
  66. package/dist/protocolTreeDocumentStorageService.d.ts +7 -7
  67. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  68. package/dist/protocolTreeDocumentStorageService.js +16 -7
  69. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  70. package/dist/retriableDocumentStorageService.js +4 -1
  71. package/dist/retriableDocumentStorageService.js.map +1 -1
  72. package/dist/serializedStateManager.d.ts +1 -2
  73. package/dist/serializedStateManager.d.ts.map +1 -1
  74. package/dist/serializedStateManager.js +10 -2
  75. package/dist/serializedStateManager.js.map +1 -1
  76. package/lib/audience.js +2 -1
  77. package/lib/audience.js.map +1 -1
  78. package/lib/catchUpMonitor.js +11 -8
  79. package/lib/catchUpMonitor.js.map +1 -1
  80. package/lib/connectionManager.d.ts +2 -2
  81. package/lib/connectionManager.d.ts.map +1 -1
  82. package/lib/connectionManager.js +91 -63
  83. package/lib/connectionManager.js.map +1 -1
  84. package/lib/connectionStateHandler.js +35 -11
  85. package/lib/connectionStateHandler.js.map +1 -1
  86. package/lib/container.d.ts +2 -2
  87. package/lib/container.d.ts.map +1 -1
  88. package/lib/container.js +156 -123
  89. package/lib/container.js.map +1 -1
  90. package/lib/containerContext.d.ts +2 -2
  91. package/lib/containerContext.d.ts.map +1 -1
  92. package/lib/containerContext.js +34 -8
  93. package/lib/containerContext.js.map +1 -1
  94. package/lib/containerStorageAdapter.js +17 -9
  95. package/lib/containerStorageAdapter.js.map +1 -1
  96. package/lib/contracts.d.ts +2 -2
  97. package/lib/contracts.d.ts.map +1 -1
  98. package/lib/contracts.js.map +1 -1
  99. package/lib/debugLogger.js +2 -0
  100. package/lib/debugLogger.js.map +1 -1
  101. package/lib/deltaManager.d.ts +2 -2
  102. package/lib/deltaManager.d.ts.map +1 -1
  103. package/lib/deltaManager.js +48 -35
  104. package/lib/deltaManager.js.map +1 -1
  105. package/lib/deltaQueue.js +14 -7
  106. package/lib/deltaQueue.js.map +1 -1
  107. package/lib/error.js +5 -4
  108. package/lib/error.js.map +1 -1
  109. package/lib/index.d.ts +1 -0
  110. package/lib/index.d.ts.map +1 -1
  111. package/lib/index.js.map +1 -1
  112. package/lib/legacy.d.ts +5 -0
  113. package/lib/loader.js +5 -1
  114. package/lib/loader.js.map +1 -1
  115. package/lib/noopHeuristic.d.ts +1 -1
  116. package/lib/noopHeuristic.d.ts.map +1 -1
  117. package/lib/noopHeuristic.js +3 -1
  118. package/lib/noopHeuristic.js.map +1 -1
  119. package/lib/packageVersion.d.ts +1 -1
  120. package/lib/packageVersion.js +1 -1
  121. package/lib/packageVersion.js.map +1 -1
  122. package/lib/protocol/index.d.ts +7 -0
  123. package/lib/protocol/index.d.ts.map +1 -0
  124. package/lib/protocol/index.js +7 -0
  125. package/lib/protocol/index.js.map +1 -0
  126. package/lib/protocol/protocol.d.ts +52 -0
  127. package/lib/protocol/protocol.d.ts.map +1 -0
  128. package/lib/protocol/protocol.js +111 -0
  129. package/lib/protocol/protocol.js.map +1 -0
  130. package/lib/protocol/quorum.d.ts +185 -0
  131. package/lib/protocol/quorum.d.ts.map +1 -0
  132. package/lib/protocol/quorum.js +431 -0
  133. package/lib/protocol/quorum.js.map +1 -0
  134. package/lib/protocol.d.ts +2 -3
  135. package/lib/protocol.d.ts.map +1 -1
  136. package/lib/protocol.js +3 -1
  137. package/lib/protocol.js.map +1 -1
  138. package/lib/protocolTreeDocumentStorageService.d.ts +7 -7
  139. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  140. package/lib/protocolTreeDocumentStorageService.js +16 -7
  141. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  142. package/lib/retriableDocumentStorageService.js +4 -1
  143. package/lib/retriableDocumentStorageService.js.map +1 -1
  144. package/lib/serializedStateManager.d.ts +1 -2
  145. package/lib/serializedStateManager.d.ts.map +1 -1
  146. package/lib/serializedStateManager.js +10 -2
  147. package/lib/serializedStateManager.js.map +1 -1
  148. package/package.json +15 -13
  149. package/src/catchUpMonitor.ts +1 -1
  150. package/src/connectionManager.ts +3 -7
  151. package/src/container.ts +4 -4
  152. package/src/containerContext.ts +2 -5
  153. package/src/contracts.ts +3 -6
  154. package/src/deltaManager.ts +3 -5
  155. package/src/index.ts +7 -0
  156. package/src/noopHeuristic.ts +1 -1
  157. package/src/packageVersion.ts +1 -1
  158. package/src/protocol/README.md +10 -0
  159. package/src/protocol/index.ts +16 -0
  160. package/src/protocol/protocol.ts +185 -0
  161. package/src/protocol/quorum.ts +584 -0
  162. package/src/protocol.ts +4 -6
  163. package/src/protocolTreeDocumentStorageService.ts +16 -8
  164. package/src/serializedStateManager.ts +1 -1
  165. package/tsconfig.json +2 -0
package/lib/container.js CHANGED
@@ -189,6 +189,41 @@ export class Container extends EventEmitterWithErrorHandling {
189
189
  return container;
190
190
  }, { start: true, end: true, cancel: "generic" });
191
191
  }
192
+ // Tells if container can reconnect on losing fist connection
193
+ // If false, container gets closed on loss of connection.
194
+ _canReconnect;
195
+ clientDetailsOverride;
196
+ urlResolver;
197
+ serviceFactory;
198
+ codeLoader;
199
+ options;
200
+ scope;
201
+ subLogger;
202
+ // eslint-disable-next-line import/no-deprecated
203
+ detachedBlobStorage;
204
+ protocolHandlerBuilder;
205
+ client;
206
+ mc;
207
+ /**
208
+ * Used by the RelativeLoader to spawn a new Container for the same document. Used to create the summarizing client.
209
+ */
210
+ clone;
211
+ /**
212
+ * Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
213
+ *
214
+ * States are allowed to progress to further states:
215
+ * "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
216
+ *
217
+ * For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
218
+ *
219
+ * loading: Container has been created, but is not yet in normal/loaded state
220
+ * loaded: Container is in normal/loaded state
221
+ * closing: Container has started closing process (for re-entrancy prevention)
222
+ * disposing: Container has started disposing process (for re-entrancy prevention)
223
+ * closed: Container has closed
224
+ * disposed: Container has been disposed
225
+ */
226
+ _lifecycleState = "loading";
192
227
  setLoaded() {
193
228
  // It's conceivable the container could be closed when this is called
194
229
  // Only transition states if currently loading
@@ -231,18 +266,39 @@ export class Container extends EventEmitterWithErrorHandling {
231
266
  get disposed() {
232
267
  return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
233
268
  }
269
+ storageAdapter;
270
+ _deltaManager;
271
+ service;
272
+ _runtime;
234
273
  get runtime() {
235
274
  if (this._runtime === undefined) {
236
275
  throw new Error("Attempted to access runtime before it was defined");
237
276
  }
238
277
  return this._runtime;
239
278
  }
279
+ _protocolHandler;
240
280
  get protocolHandler() {
241
281
  if (this._protocolHandler === undefined) {
242
282
  throw new Error("Attempted to access protocolHandler before it was defined");
243
283
  }
244
284
  return this._protocolHandler;
245
285
  }
286
+ /** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
287
+ inboundQueuePausedFromInit = true;
288
+ firstConnection = true;
289
+ connectionTransitionTimes = [];
290
+ _loadedFromVersion;
291
+ _dirtyContainer = false;
292
+ attachmentData = { state: AttachState.Detached };
293
+ serializedStateManager;
294
+ _containerId;
295
+ lastVisible;
296
+ visibilityEventHandler;
297
+ connectionStateHandler;
298
+ clientsWhoShouldHaveLeft = new Set();
299
+ _containerMetadata = {};
300
+ setAutoReconnectTime = performance.now();
301
+ noopHeuristic;
246
302
  get connectionMode() {
247
303
  return this._deltaManager.connectionManager.connectionMode;
248
304
  }
@@ -313,6 +369,7 @@ export class Container extends EventEmitterWithErrorHandling {
313
369
  getSpecifiedCodeDetails() {
314
370
  return this.getCodeDetailsFromQuorum();
315
371
  }
372
+ _loadedCodeDetails;
316
373
  /**
317
374
  * Get the code details that were used to load the container.
318
375
  * @returns The code details that were used to load the container if it is loaded, undefined if it is not yet
@@ -321,6 +378,7 @@ export class Container extends EventEmitterWithErrorHandling {
321
378
  getLoadedCodeDetails() {
322
379
  return this._loadedCodeDetails;
323
380
  }
381
+ _loadedModule;
324
382
  /**
325
383
  * Retrieves the audience associated with the document
326
384
  */
@@ -359,6 +417,7 @@ export class Container extends EventEmitterWithErrorHandling {
359
417
  this._lifecycleEvents.once("disposed", disposedHandler);
360
418
  });
361
419
  }
420
+ _lifecycleEvents = new TypedEventEmitter();
362
421
  constructor(createProps, loadProps) {
363
422
  super((name, error) => {
364
423
  this.mc.logger.sendErrorEvent({
@@ -367,128 +426,6 @@ export class Container extends EventEmitterWithErrorHandling {
367
426
  }, error);
368
427
  this.close(normalizeError(error));
369
428
  });
370
- /**
371
- * Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
372
- *
373
- * States are allowed to progress to further states:
374
- * "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
375
- *
376
- * For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
377
- *
378
- * loading: Container has been created, but is not yet in normal/loaded state
379
- * loaded: Container is in normal/loaded state
380
- * closing: Container has started closing process (for re-entrancy prevention)
381
- * disposing: Container has started disposing process (for re-entrancy prevention)
382
- * closed: Container has closed
383
- * disposed: Container has been disposed
384
- */
385
- this._lifecycleState = "loading";
386
- /** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
387
- this.inboundQueuePausedFromInit = true;
388
- this.firstConnection = true;
389
- this.connectionTransitionTimes = [];
390
- this._dirtyContainer = false;
391
- this.attachmentData = { state: AttachState.Detached };
392
- this.clientsWhoShouldHaveLeft = new Set();
393
- this._containerMetadata = {};
394
- this.setAutoReconnectTime = performance.now();
395
- this._lifecycleEvents = new TypedEventEmitter();
396
- this._disposed = false;
397
- this.attach = runSingle(async (request, attachProps) => {
398
- await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
399
- if (this._lifecycleState !== "loaded" ||
400
- this.attachmentData.state === AttachState.Attached) {
401
- // pre-0.58 error message: containerNotValidForAttach
402
- throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}] and [${this.attachState}]`);
403
- }
404
- const normalizeErrorAndClose = (error) => {
405
- const newError = normalizeError(error);
406
- this.close(newError);
407
- // add resolved URL on error object so that host has the ability to find this document and delete it
408
- newError.addTelemetryProperties({
409
- resolvedUrl: this.service?.resolvedUrl?.url,
410
- });
411
- return newError;
412
- };
413
- const setAttachmentData = (attachmentData) => {
414
- const previousState = this.attachmentData.state;
415
- this.attachmentData = attachmentData;
416
- const state = this.attachmentData.state;
417
- if (state !== previousState && state !== AttachState.Detached) {
418
- try {
419
- this.runtime.setAttachState(state);
420
- this.emit(state.toLocaleLowerCase());
421
- }
422
- catch (error) {
423
- throw normalizeErrorAndClose(error);
424
- }
425
- }
426
- };
427
- const createAttachmentSummary = (redirectTable) => {
428
- try {
429
- assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
430
- return combineAppAndProtocolSummary(this.runtime.createSummary(redirectTable), this.captureProtocolSummary());
431
- }
432
- catch (error) {
433
- throw normalizeErrorAndClose(error);
434
- }
435
- };
436
- const createOrGetStorageService = async (summary) => {
437
- // Actually go and create the resolved document
438
- if (this.service === undefined) {
439
- const createNewResolvedUrl = await this.urlResolver.resolve(request);
440
- assert(this.client.details.type !== summarizerClientType &&
441
- createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
442
- this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
443
- cancel: this._deltaManager.closeAbortController.signal,
444
- });
445
- }
446
- this.storageAdapter.connectToService(this.service);
447
- return this.storageAdapter;
448
- };
449
- let attachP = runRetriableAttachProcess({
450
- initialAttachmentData: this.attachmentData,
451
- offlineLoadEnabled: this.serializedStateManager.offlineLoadEnabled,
452
- detachedBlobStorage: this.detachedBlobStorage,
453
- setAttachmentData,
454
- createAttachmentSummary,
455
- createOrGetStorageService,
456
- });
457
- // only enable the new behavior if the config is set
458
- if (this.mc.config.getBoolean("Fluid.Container.RetryOnAttachFailure") !== true) {
459
- attachP = attachP.catch((error) => {
460
- throw normalizeErrorAndClose(error);
461
- });
462
- }
463
- // If offline load is enabled, attachP will return the attach summary (in Snapshot format) so we can initialize SerializedStateManager
464
- const snapshotWithBlobs = await attachP;
465
- this.serializedStateManager.setInitialSnapshot(snapshotWithBlobs);
466
- if (!this.closed) {
467
- this.detachedBlobStorage.dispose?.();
468
- this.handleDeltaConnectionArg(attachProps?.deltaConnection, {
469
- fetchOpsFromStorage: false,
470
- reason: { text: "createDetached" },
471
- });
472
- }
473
- }, { start: true, end: true, cancel: "generic" });
474
- });
475
- this.getAbsoluteUrl = async (relativeUrl) => {
476
- if (this.resolvedUrl === undefined) {
477
- return undefined;
478
- }
479
- return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName(this._loadedCodeDetails));
480
- };
481
- this.metadataUpdateHandler = (metadata) => {
482
- this._containerMetadata = { ...this._containerMetadata, ...metadata };
483
- this.emit("metadataUpdate", metadata);
484
- };
485
- this.updateDirtyContainerState = (dirty) => {
486
- if (this._dirtyContainer === dirty) {
487
- return;
488
- }
489
- this._dirtyContainer = dirty;
490
- this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
491
- };
492
429
  const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
493
430
  this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
494
431
  const pendingLocalState = loadProps?.pendingLocalState;
@@ -633,7 +570,7 @@ export class Container extends EventEmitterWithErrorHandling {
633
570
  const offlineLoadEnabled = (this.isInteractiveClient &&
634
571
  this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) ??
635
572
  options.enableOfflineLoad === true;
636
- this.serializedStateManager = new SerializedStateManager(pendingLocalState, this.subLogger, this.storageAdapter, offlineLoadEnabled, this, () => this.isDirty);
573
+ this.serializedStateManager = new SerializedStateManager(pendingLocalState, this.subLogger, this.storageAdapter, offlineLoadEnabled, this, () => this._deltaManager.connectionManager.shouldJoinWrite());
637
574
  const isDomAvailable = typeof document === "object" &&
638
575
  document !== null &&
639
576
  typeof document.addEventListener === "function" &&
@@ -716,6 +653,7 @@ export class Container extends EventEmitterWithErrorHandling {
716
653
  }
717
654
  }
718
655
  }
656
+ _disposed = false;
719
657
  disposeCore(error) {
720
658
  assert(!this._disposed, 0x54c /* Container already disposed */);
721
659
  this._disposed = true;
@@ -814,6 +752,84 @@ export class Container extends EventEmitterWithErrorHandling {
814
752
  };
815
753
  return JSON.stringify(detachedContainerState);
816
754
  }
755
+ attach = runSingle(async (request, attachProps) => {
756
+ await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
757
+ if (this._lifecycleState !== "loaded" ||
758
+ this.attachmentData.state === AttachState.Attached) {
759
+ // pre-0.58 error message: containerNotValidForAttach
760
+ throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}] and [${this.attachState}]`);
761
+ }
762
+ const normalizeErrorAndClose = (error) => {
763
+ const newError = normalizeError(error);
764
+ this.close(newError);
765
+ // add resolved URL on error object so that host has the ability to find this document and delete it
766
+ newError.addTelemetryProperties({
767
+ resolvedUrl: this.service?.resolvedUrl?.url,
768
+ });
769
+ return newError;
770
+ };
771
+ const setAttachmentData = (attachmentData) => {
772
+ const previousState = this.attachmentData.state;
773
+ this.attachmentData = attachmentData;
774
+ const state = this.attachmentData.state;
775
+ if (state !== previousState && state !== AttachState.Detached) {
776
+ try {
777
+ this.runtime.setAttachState(state);
778
+ this.emit(state.toLocaleLowerCase());
779
+ }
780
+ catch (error) {
781
+ throw normalizeErrorAndClose(error);
782
+ }
783
+ }
784
+ };
785
+ const createAttachmentSummary = (redirectTable) => {
786
+ try {
787
+ assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
788
+ return combineAppAndProtocolSummary(this.runtime.createSummary(redirectTable), this.captureProtocolSummary());
789
+ }
790
+ catch (error) {
791
+ throw normalizeErrorAndClose(error);
792
+ }
793
+ };
794
+ const createOrGetStorageService = async (summary) => {
795
+ // Actually go and create the resolved document
796
+ if (this.service === undefined) {
797
+ const createNewResolvedUrl = await this.urlResolver.resolve(request);
798
+ assert(this.client.details.type !== summarizerClientType &&
799
+ createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
800
+ this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
801
+ cancel: this._deltaManager.closeAbortController.signal,
802
+ });
803
+ }
804
+ this.storageAdapter.connectToService(this.service);
805
+ return this.storageAdapter;
806
+ };
807
+ let attachP = runRetriableAttachProcess({
808
+ initialAttachmentData: this.attachmentData,
809
+ offlineLoadEnabled: this.serializedStateManager.offlineLoadEnabled,
810
+ detachedBlobStorage: this.detachedBlobStorage,
811
+ setAttachmentData,
812
+ createAttachmentSummary,
813
+ createOrGetStorageService,
814
+ });
815
+ // only enable the new behavior if the config is set
816
+ if (this.mc.config.getBoolean("Fluid.Container.RetryOnAttachFailure") !== true) {
817
+ attachP = attachP.catch((error) => {
818
+ throw normalizeErrorAndClose(error);
819
+ });
820
+ }
821
+ // If offline load is enabled, attachP will return the attach summary (in Snapshot format) so we can initialize SerializedStateManager
822
+ const snapshotWithBlobs = await attachP;
823
+ this.serializedStateManager.setInitialSnapshot(snapshotWithBlobs);
824
+ if (!this.closed) {
825
+ this.detachedBlobStorage.dispose?.();
826
+ this.handleDeltaConnectionArg(attachProps?.deltaConnection, {
827
+ fetchOpsFromStorage: false,
828
+ reason: { text: "createDetached" },
829
+ });
830
+ }
831
+ }, { start: true, end: true, cancel: "generic" });
832
+ });
817
833
  setAutoReconnectInternal(mode, reason) {
818
834
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
819
835
  if (currentMode === mode) {
@@ -886,6 +902,12 @@ export class Container extends EventEmitterWithErrorHandling {
886
902
  // Ensure connection to web socket
887
903
  this.connectToDeltaStream(args);
888
904
  }
905
+ getAbsoluteUrl = async (relativeUrl) => {
906
+ if (this.resolvedUrl === undefined) {
907
+ return undefined;
908
+ }
909
+ return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName(this._loadedCodeDetails));
910
+ };
889
911
  async proposeCodeDetails(codeDetails) {
890
912
  if (!isFluidCodeDetails(codeDetails)) {
891
913
  throw new Error("Provided codeDetails are not IFluidCodeDetails");
@@ -954,6 +976,10 @@ export class Container extends EventEmitterWithErrorHandling {
954
976
  }
955
977
  this._deltaManager.connect(args);
956
978
  }
979
+ metadataUpdateHandler = (metadata) => {
980
+ this._containerMetadata = { ...this._containerMetadata, ...metadata };
981
+ this.emit("metadataUpdate", metadata);
982
+ };
957
983
  async createDocumentService(serviceProvider) {
958
984
  const service = await serviceProvider();
959
985
  // Back-compat for Old driver
@@ -1499,6 +1525,13 @@ export class Container extends EventEmitterWithErrorHandling {
1499
1525
  this._lifecycleEvents.emit("runtimeInstantiated");
1500
1526
  this._loadedCodeDetails = codeDetails;
1501
1527
  }
1528
+ updateDirtyContainerState = (dirty) => {
1529
+ if (this._dirtyContainer === dirty) {
1530
+ return;
1531
+ }
1532
+ this._dirtyContainer = dirty;
1533
+ this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
1534
+ };
1502
1535
  /**
1503
1536
  * Set the connected state of the ContainerContext
1504
1537
  * This controls the "connected" state of the ContainerRuntime as well