@fluidframework/container-loader 2.0.0-dev-rc.1.0.0.225277 → 2.0.0-dev-rc.1.0.0.232845

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 (159) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +3 -9
  3. package/api-report/container-loader.api.md +1 -1
  4. package/dist/attachment.d.ts +116 -0
  5. package/dist/attachment.d.ts.map +1 -0
  6. package/dist/attachment.js +82 -0
  7. package/dist/attachment.js.map +1 -0
  8. package/dist/{audience.cjs → audience.js} +1 -1
  9. package/dist/audience.js.map +1 -0
  10. package/dist/{catchUpMonitor.cjs → catchUpMonitor.js} +1 -1
  11. package/dist/catchUpMonitor.js.map +1 -0
  12. package/dist/connectionManager.d.ts.map +1 -1
  13. package/dist/{connectionManager.cjs → connectionManager.js} +6 -7
  14. package/dist/connectionManager.js.map +1 -0
  15. package/dist/connectionState.d.ts +1 -1
  16. package/dist/{connectionState.cjs → connectionState.js} +2 -2
  17. package/dist/{connectionState.cjs.map → connectionState.js.map} +1 -1
  18. package/dist/{connectionStateHandler.cjs → connectionStateHandler.js} +3 -3
  19. package/dist/connectionStateHandler.js.map +1 -0
  20. package/dist/container-loader-alpha.d.ts +2 -2
  21. package/dist/container-loader-beta.d.ts +24 -1
  22. package/dist/container-loader-public.d.ts +24 -1
  23. package/dist/container-loader-untrimmed.d.ts +2 -2
  24. package/dist/container.d.ts +21 -8
  25. package/dist/container.d.ts.map +1 -1
  26. package/dist/{container.cjs → container.js} +192 -164
  27. package/dist/container.js.map +1 -0
  28. package/dist/containerContext.d.ts +3 -2
  29. package/dist/containerContext.d.ts.map +1 -1
  30. package/dist/{containerContext.cjs → containerContext.js} +3 -2
  31. package/dist/containerContext.js.map +1 -0
  32. package/dist/containerStorageAdapter.d.ts +3 -3
  33. package/dist/containerStorageAdapter.d.ts.map +1 -1
  34. package/dist/{containerStorageAdapter.cjs → containerStorageAdapter.js} +13 -14
  35. package/dist/containerStorageAdapter.js.map +1 -0
  36. package/dist/{contracts.cjs → contracts.js} +1 -1
  37. package/dist/contracts.js.map +1 -0
  38. package/dist/{debugLogger.cjs → debugLogger.js} +1 -1
  39. package/dist/debugLogger.js.map +1 -0
  40. package/dist/{deltaManager.cjs → deltaManager.js} +3 -3
  41. package/dist/deltaManager.js.map +1 -0
  42. package/dist/{deltaQueue.cjs → deltaQueue.js} +1 -1
  43. package/dist/deltaQueue.js.map +1 -0
  44. package/dist/{disposal.cjs → disposal.js} +1 -1
  45. package/dist/disposal.js.map +1 -0
  46. package/dist/{error.cjs → error.js} +1 -1
  47. package/dist/error.js.map +1 -0
  48. package/dist/{index.cjs → index.js} +6 -6
  49. package/dist/index.js.map +1 -0
  50. package/dist/loader.d.ts +1 -1
  51. package/dist/{loader.cjs → loader.js} +5 -5
  52. package/dist/{loader.cjs.map → loader.js.map} +1 -1
  53. package/dist/location-redirection-utilities/{index.cjs → index.js} +2 -2
  54. package/dist/location-redirection-utilities/index.js.map +1 -0
  55. package/dist/location-redirection-utilities/{resolveWithLocationRedirection.cjs → resolveWithLocationRedirection.js} +1 -1
  56. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  57. package/dist/{noopHeuristic.cjs → noopHeuristic.js} +1 -1
  58. package/dist/noopHeuristic.js.map +1 -0
  59. package/dist/packageVersion.d.ts +1 -1
  60. package/dist/{packageVersion.cjs → packageVersion.js} +2 -2
  61. package/dist/packageVersion.js.map +1 -0
  62. package/dist/{protocol.cjs → protocol.js} +1 -1
  63. package/dist/protocol.js.map +1 -0
  64. package/dist/protocolTreeDocumentStorageService.d.ts +1 -0
  65. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  66. package/dist/{protocolTreeDocumentStorageService.cjs → protocolTreeDocumentStorageService.js} +2 -1
  67. package/dist/protocolTreeDocumentStorageService.js.map +1 -0
  68. package/dist/{quorum.cjs → quorum.js} +1 -1
  69. package/dist/quorum.js.map +1 -0
  70. package/dist/retriableDocumentStorageService.d.ts +2 -1
  71. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  72. package/dist/{retriableDocumentStorageService.cjs → retriableDocumentStorageService.js} +9 -1
  73. package/dist/retriableDocumentStorageService.js.map +1 -0
  74. package/dist/tsdoc-metadata.json +1 -1
  75. package/dist/utils.d.ts +13 -7
  76. package/dist/utils.d.ts.map +1 -1
  77. package/dist/utils.js +211 -0
  78. package/dist/utils.js.map +1 -0
  79. package/lib/attachment.d.mts +112 -0
  80. package/lib/attachment.d.mts.map +1 -0
  81. package/lib/attachment.mjs +74 -0
  82. package/lib/attachment.mjs.map +1 -0
  83. package/lib/connectionManager.d.mts.map +1 -1
  84. package/lib/connectionManager.mjs +2 -5
  85. package/lib/connectionManager.mjs.map +1 -1
  86. package/lib/connectionState.d.mts +1 -1
  87. package/lib/connectionState.mjs +1 -1
  88. package/lib/connectionState.mjs.map +1 -1
  89. package/lib/container-loader-alpha.d.mts +2 -2
  90. package/lib/container-loader-beta.d.mts +24 -1
  91. package/lib/container-loader-public.d.mts +24 -1
  92. package/lib/container-loader-untrimmed.d.mts +2 -2
  93. package/lib/container.d.mts +21 -8
  94. package/lib/container.d.mts.map +1 -1
  95. package/lib/container.mjs +176 -151
  96. package/lib/container.mjs.map +1 -1
  97. package/lib/containerContext.d.mts +3 -2
  98. package/lib/containerContext.d.mts.map +1 -1
  99. package/lib/containerContext.mjs +2 -1
  100. package/lib/containerContext.mjs.map +1 -1
  101. package/lib/containerStorageAdapter.d.mts +3 -3
  102. package/lib/containerStorageAdapter.d.mts.map +1 -1
  103. package/lib/containerStorageAdapter.mjs +10 -11
  104. package/lib/containerStorageAdapter.mjs.map +1 -1
  105. package/lib/loader.d.mts +1 -1
  106. package/lib/loader.mjs.map +1 -1
  107. package/lib/packageVersion.d.mts +1 -1
  108. package/lib/packageVersion.mjs +1 -1
  109. package/lib/packageVersion.mjs.map +1 -1
  110. package/lib/protocolTreeDocumentStorageService.d.mts +1 -0
  111. package/lib/protocolTreeDocumentStorageService.d.mts.map +1 -1
  112. package/lib/protocolTreeDocumentStorageService.mjs +1 -0
  113. package/lib/protocolTreeDocumentStorageService.mjs.map +1 -1
  114. package/lib/retriableDocumentStorageService.d.mts +2 -1
  115. package/lib/retriableDocumentStorageService.d.mts.map +1 -1
  116. package/lib/retriableDocumentStorageService.mjs +9 -1
  117. package/lib/retriableDocumentStorageService.mjs.map +1 -1
  118. package/lib/utils.d.mts +13 -7
  119. package/lib/utils.d.mts.map +1 -1
  120. package/lib/utils.mjs +96 -29
  121. package/lib/utils.mjs.map +1 -1
  122. package/package.json +36 -30
  123. package/src/attachment.ts +219 -0
  124. package/src/connectionManager.ts +2 -4
  125. package/src/connectionState.ts +1 -1
  126. package/src/container.ts +260 -191
  127. package/src/containerContext.ts +2 -1
  128. package/src/containerStorageAdapter.ts +15 -12
  129. package/src/loader.ts +1 -1
  130. package/src/packageVersion.ts +1 -1
  131. package/src/protocolTreeDocumentStorageService.ts +1 -0
  132. package/src/retriableDocumentStorageService.ts +18 -1
  133. package/src/utils.ts +128 -36
  134. package/dist/audience.cjs.map +0 -1
  135. package/dist/catchUpMonitor.cjs.map +0 -1
  136. package/dist/connectionManager.cjs.map +0 -1
  137. package/dist/connectionStateHandler.cjs.map +0 -1
  138. package/dist/container.cjs.map +0 -1
  139. package/dist/containerContext.cjs.map +0 -1
  140. package/dist/containerStorageAdapter.cjs.map +0 -1
  141. package/dist/contracts.cjs.map +0 -1
  142. package/dist/debugLogger.cjs.map +0 -1
  143. package/dist/deltaManager.cjs.map +0 -1
  144. package/dist/deltaQueue.cjs.map +0 -1
  145. package/dist/disposal.cjs.map +0 -1
  146. package/dist/error.cjs.map +0 -1
  147. package/dist/index.cjs.map +0 -1
  148. package/dist/location-redirection-utilities/index.cjs.map +0 -1
  149. package/dist/location-redirection-utilities/resolveWithLocationRedirection.cjs.map +0 -1
  150. package/dist/noopHeuristic.cjs.map +0 -1
  151. package/dist/packageVersion.cjs.map +0 -1
  152. package/dist/protocol.cjs.map +0 -1
  153. package/dist/protocolTreeDocumentStorageService.cjs.map +0 -1
  154. package/dist/quorum.cjs.map +0 -1
  155. package/dist/retriableDocumentStorageService.cjs.map +0 -1
  156. package/dist/utils.cjs +0 -142
  157. package/dist/utils.cjs.map +0 -1
  158. package/tsc-multi.test.json +0 -4
  159. /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -3,6 +3,9 @@
3
3
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
4
  * Licensed under the MIT License.
5
5
  */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
6
9
  Object.defineProperty(exports, "__esModule", { value: true });
7
10
  exports.Container = exports.ReportIfTooLong = exports.waitContainerToCatchUp = void 0;
8
11
  const uuid_1 = require("uuid");
@@ -13,25 +16,26 @@ const container_definitions_1 = require("@fluidframework/container-definitions")
13
16
  const driver_utils_1 = require("@fluidframework/driver-utils");
14
17
  const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
15
18
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
16
- const audience_1 = require("./audience.cjs");
17
- const containerContext_1 = require("./containerContext.cjs");
18
- const contracts_1 = require("./contracts.cjs");
19
- const deltaManager_1 = require("./deltaManager.cjs");
20
- const loader_1 = require("./loader.cjs");
21
- const packageVersion_1 = require("./packageVersion.cjs");
22
- const containerStorageAdapter_1 = require("./containerStorageAdapter.cjs");
23
- const connectionStateHandler_1 = require("./connectionStateHandler.cjs");
24
- const utils_1 = require("./utils.cjs");
25
- const quorum_1 = require("./quorum.cjs");
26
- const noopHeuristic_1 = require("./noopHeuristic.cjs");
27
- const connectionManager_1 = require("./connectionManager.cjs");
28
- const connectionState_1 = require("./connectionState.cjs");
29
- const protocol_1 = require("./protocol.cjs");
19
+ const structured_clone_1 = __importDefault(require("@ungap/structured-clone"));
20
+ const audience_1 = require("./audience");
21
+ const containerContext_1 = require("./containerContext");
22
+ const contracts_1 = require("./contracts");
23
+ const deltaManager_1 = require("./deltaManager");
24
+ const loader_1 = require("./loader");
25
+ const packageVersion_1 = require("./packageVersion");
26
+ const containerStorageAdapter_1 = require("./containerStorageAdapter");
27
+ const connectionStateHandler_1 = require("./connectionStateHandler");
28
+ const utils_1 = require("./utils");
29
+ const quorum_1 = require("./quorum");
30
+ const noopHeuristic_1 = require("./noopHeuristic");
31
+ const connectionManager_1 = require("./connectionManager");
32
+ const connectionState_1 = require("./connectionState");
33
+ const protocol_1 = require("./protocol");
34
+ const attachment_1 = require("./attachment");
30
35
  const detachedContainerRefSeqNumber = 0;
31
36
  const dirtyContainerEvent = "dirty";
32
37
  const savedContainerEvent = "saved";
33
38
  const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
34
- const hasBlobsSummaryTree = ".hasAttachmentBlobs";
35
39
  /**
36
40
  * Waits until container connects to delta storage and gets up-to-date.
37
41
  *
@@ -185,11 +189,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
185
189
  static async rehydrateDetachedFromSnapshot(createProps, snapshot) {
186
190
  const container = new Container(createProps);
187
191
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
188
- const deserializedSummary = JSON.parse(snapshot);
189
- if (!(0, driver_utils_1.isCombinedAppAndProtocolSummary)(deserializedSummary, hasBlobsSummaryTree)) {
190
- throw new telemetry_utils_1.UsageError("Cannot rehydrate detached container. Incorrect format");
191
- }
192
- await container.rehydrateDetachedFromSnapshot(deserializedSummary);
192
+ const detachedContainerState = (0, utils_1.getDetachedContainerStateFromSerializedContainer)(snapshot);
193
+ await container.rehydrateDetachedFromSnapshot(detachedContainerState);
193
194
  return container;
194
195
  }, { start: true, end: true, cancel: "generic" });
195
196
  }
@@ -240,6 +241,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
240
241
  get readOnlyInfo() {
241
242
  return this._deltaManager.readOnlyInfo;
242
243
  }
244
+ get containerMetadata() {
245
+ return this._containerMetadata;
246
+ }
243
247
  /**
244
248
  * Sends signal to runtime (and data stores) to be read-only.
245
249
  * Hosts may have read only views, indicating to data stores that no edits are allowed.
@@ -361,24 +365,103 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
361
365
  * disposed: Container has been disposed
362
366
  */
363
367
  this._lifecycleState = "loading";
364
- this._attachState = container_definitions_1.AttachState.Detached;
365
368
  /** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
366
369
  this.inboundQueuePausedFromInit = true;
367
370
  this.firstConnection = true;
368
371
  this.connectionTransitionTimes = [];
369
- this.attachStarted = false;
370
372
  this._dirtyContainer = false;
371
373
  this.savedOps = [];
374
+ this.attachmentData = { state: container_definitions_1.AttachState.Detached };
372
375
  this.clientsWhoShouldHaveLeft = new Set();
376
+ this._containerMetadata = {};
373
377
  this.setAutoReconnectTime = client_utils_1.performance.now();
374
378
  this._lifecycleEvents = new client_utils_1.TypedEventEmitter();
375
379
  this._disposed = false;
380
+ this.attach = (0, utils_1.runSingle)(async (request, attachProps) => {
381
+ await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
382
+ if (this._lifecycleState !== "loaded" ||
383
+ this.attachmentData.state === container_definitions_1.AttachState.Attached) {
384
+ // pre-0.58 error message: containerNotValidForAttach
385
+ throw new telemetry_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}] and [${this.attachState}]`);
386
+ }
387
+ const normalizeErrorAndClose = (error) => {
388
+ const newError = (0, telemetry_utils_1.normalizeError)(error);
389
+ this.close(newError);
390
+ // add resolved URL on error object so that host has the ability to find this document and delete it
391
+ newError.addTelemetryProperties({
392
+ resolvedUrl: this.service?.resolvedUrl?.url,
393
+ });
394
+ return newError;
395
+ };
396
+ const setAttachmentData = (attachmentData) => {
397
+ const previousState = this.attachmentData.state;
398
+ this.attachmentData = attachmentData;
399
+ const state = this.attachmentData.state;
400
+ if (state !== previousState && state !== container_definitions_1.AttachState.Detached) {
401
+ try {
402
+ this.runtime.setAttachState(state);
403
+ this.emit(state.toLocaleLowerCase());
404
+ }
405
+ catch (error) {
406
+ throw normalizeErrorAndClose(error);
407
+ }
408
+ }
409
+ };
410
+ const createAttachmentSummary = (redirectTable) => {
411
+ try {
412
+ (0, core_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
413
+ return (0, utils_1.combineAppAndProtocolSummary)(this.runtime.createSummary(redirectTable), this.captureProtocolSummary());
414
+ }
415
+ catch (error) {
416
+ throw normalizeErrorAndClose(error);
417
+ }
418
+ };
419
+ const createOrGetStorageService = async (summary) => {
420
+ // Actually go and create the resolved document
421
+ if (this.service === undefined) {
422
+ const createNewResolvedUrl = await this.urlResolver.resolve(request);
423
+ (0, core_utils_1.assert)(this.client.details.type !== summarizerClientType &&
424
+ createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
425
+ this.service = await (0, driver_utils_1.runWithRetry)(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
426
+ cancel: this._deltaManager.closeAbortController.signal,
427
+ });
428
+ }
429
+ this.storageAdapter.connectToService(this.service);
430
+ return this.storageAdapter;
431
+ };
432
+ let attachP = (0, attachment_1.runRetriableAttachProcess)({
433
+ initialAttachmentData: this.attachmentData,
434
+ offlineLoadEnabled: this.offlineLoadEnabled,
435
+ detachedBlobStorage: this.detachedBlobStorage,
436
+ setAttachmentData,
437
+ createAttachmentSummary,
438
+ createOrGetStorageService,
439
+ });
440
+ // only enable the new behavior if the config is set
441
+ if (this.mc.config.getBoolean("Fluid.Container.RetryOnAttachFailure") !== true) {
442
+ attachP = attachP.catch((error) => {
443
+ throw normalizeErrorAndClose(error);
444
+ });
445
+ }
446
+ await attachP;
447
+ if (!this.closed) {
448
+ this.handleDeltaConnectionArg({
449
+ fetchOpsFromStorage: false,
450
+ reason: { text: "createDetached" },
451
+ }, attachProps?.deltaConnection);
452
+ }
453
+ }, { start: true, end: true, cancel: "generic" });
454
+ });
376
455
  this.getAbsoluteUrl = async (relativeUrl) => {
377
456
  if (this.resolvedUrl === undefined) {
378
457
  return undefined;
379
458
  }
380
459
  return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)(this._loadedCodeDetails));
381
460
  };
461
+ this.metadataUpdateHandler = (metadata) => {
462
+ this._containerMetadata = { ...this._containerMetadata, ...metadata };
463
+ this.emit("metadataUpdate", metadata);
464
+ };
382
465
  this.updateDirtyContainerState = (dirty) => {
383
466
  if (this._dirtyContainer === dirty) {
384
467
  return;
@@ -426,7 +509,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
426
509
  clientType,
427
510
  containerId: this._containerId,
428
511
  docId: () => this.resolvedUrl?.id,
429
- containerAttachState: () => this._attachState,
512
+ containerAttachState: () => this.attachState,
430
513
  containerLifecycleState: () => this._lifecycleState,
431
514
  containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
432
515
  serializedContainer: pendingLocalState !== undefined,
@@ -577,6 +660,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
577
660
  : "generic",
578
661
  }, error);
579
662
  this._lifecycleState = "closing";
663
+ // Back-compat for Old driver
664
+ if (this.service?.off !== undefined) {
665
+ this.service?.off("metadataUpdate", this.metadataUpdateHandler);
666
+ }
580
667
  this._protocolHandler?.close();
581
668
  this.connectionStateHandler.dispose();
582
669
  }
@@ -664,15 +751,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
664
751
  if (this.closed || this._disposed) {
665
752
  throw new telemetry_utils_1.UsageError("Pending state cannot be retried if the container is closed or disposed");
666
753
  }
667
- (0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
754
+ (0, core_utils_1.assert)(this.attachmentData.state === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
668
755
  (0, core_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
669
- (0, core_utils_1.assert)(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
670
- (0, core_utils_1.assert)(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
756
+ (0, core_utils_1.assert)(this.attachmentData.snapshot !== undefined, 0x5d5 /* no base data */);
671
757
  const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
672
758
  const pendingState = {
673
759
  pendingRuntimeState,
674
- baseSnapshot: this.baseSnapshot,
675
- snapshotBlobs: this.baseSnapshotBlobs,
760
+ baseSnapshot: this.attachmentData.snapshot.tree,
761
+ snapshotBlobs: this.attachmentData.snapshot.blobs,
676
762
  savedOps: this.savedOps,
677
763
  url: this.resolvedUrl.url,
678
764
  // no need to save this if there is no pending runtime state
@@ -682,119 +768,21 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
682
768
  });
683
769
  }
684
770
  get attachState() {
685
- return this._attachState;
771
+ return this.attachmentData.state;
686
772
  }
687
773
  serialize() {
688
774
  (0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
689
775
  const appSummary = this.runtime.createSummary();
690
776
  const protocolSummary = this.captureProtocolSummary();
691
777
  const combinedSummary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
692
- if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
693
- combinedSummary.tree[hasBlobsSummaryTree] = {
694
- type: protocol_definitions_1.SummaryType.Blob,
695
- content: "true",
696
- };
697
- }
698
- return JSON.stringify(combinedSummary);
699
- }
700
- async attach(request, attachProps) {
701
- await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
702
- if (this._lifecycleState !== "loaded") {
703
- // pre-0.58 error message: containerNotValidForAttach
704
- throw new telemetry_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
705
- }
706
- // If container is already attached or attach is in progress, throw an error.
707
- (0, core_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
708
- this.attachStarted = true;
709
- // If attachment blobs were uploaded in detached state we will go through a different attach flow
710
- const hasAttachmentBlobs = this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
711
- try {
712
- (0, core_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
713
- let summary;
714
- if (!hasAttachmentBlobs) {
715
- // Get the document state post attach - possibly can just call attach but we need to change the
716
- // semantics around what the attach means as far as async code goes.
717
- const appSummary = this.runtime.createSummary();
718
- const protocolSummary = this.captureProtocolSummary();
719
- summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
720
- // Set the state as attaching as we are starting the process of attaching container.
721
- // This should be fired after taking the summary because it is the place where we are
722
- // starting to attach the container to storage.
723
- // Also, this should only be fired in detached container.
724
- this._attachState = container_definitions_1.AttachState.Attaching;
725
- this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
726
- this.emit("attaching");
727
- if (this.offlineLoadEnabled) {
728
- const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
729
- this.baseSnapshot = snapshot;
730
- this.baseSnapshotBlobs =
731
- (0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
732
- }
733
- }
734
- // Actually go and create the resolved document
735
- if (this.service === undefined) {
736
- const createNewResolvedUrl = await this.urlResolver.resolve(request);
737
- (0, core_utils_1.assert)(this.client.details.type !== summarizerClientType &&
738
- createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
739
- this.service = await (0, driver_utils_1.runWithRetry)(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
740
- cancel: this._deltaManager.closeAbortController.signal,
741
- });
742
- }
743
- this.storageAdapter.connectToService(this.service);
744
- if (hasAttachmentBlobs) {
745
- // upload blobs to storage
746
- (0, core_utils_1.assert)(!!this.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
747
- // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
748
- // support blob handles that only know about the local IDs
749
- const redirectTable = new Map();
750
- // if new blobs are added while uploading, upload them too
751
- while (redirectTable.size < this.detachedBlobStorage.size) {
752
- const newIds = this.detachedBlobStorage
753
- .getBlobIds()
754
- .filter((id) => !redirectTable.has(id));
755
- for (const id of newIds) {
756
- const blob = await this.detachedBlobStorage.readBlob(id);
757
- const response = await this.storageAdapter.createBlob(blob);
758
- redirectTable.set(id, response.id);
759
- }
760
- }
761
- // take summary and upload
762
- const appSummary = this.runtime.createSummary(redirectTable);
763
- const protocolSummary = this.captureProtocolSummary();
764
- summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
765
- this._attachState = container_definitions_1.AttachState.Attaching;
766
- this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
767
- this.emit("attaching");
768
- if (this.offlineLoadEnabled) {
769
- const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
770
- this.baseSnapshot = snapshot;
771
- this.baseSnapshotBlobs =
772
- (0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
773
- }
774
- await this.storageAdapter.uploadSummaryWithContext(summary, {
775
- referenceSequenceNumber: 0,
776
- ackHandle: undefined,
777
- proposalHandle: undefined,
778
- });
779
- }
780
- this._attachState = container_definitions_1.AttachState.Attached;
781
- this.runtime.setAttachState(container_definitions_1.AttachState.Attached);
782
- this.emit("attached");
783
- if (!this.closed) {
784
- this.handleDeltaConnectionArg({
785
- fetchOpsFromStorage: false,
786
- reason: { text: "createDetached" },
787
- }, attachProps?.deltaConnection);
788
- }
789
- }
790
- catch (error) {
791
- // add resolved URL on error object so that host has the ability to find this document and delete it
792
- const newError = (0, telemetry_utils_1.normalizeError)(error);
793
- newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
794
- this.close(newError);
795
- throw newError;
796
- }
797
- }, { start: true, end: true, cancel: "generic" });
778
+ const { tree: snapshot, blobs } = (0, utils_1.getSnapshotTreeAndBlobsFromSerializedContainer)(combinedSummary);
779
+ const detachedContainerState = {
780
+ attached: false,
781
+ baseSnapshot: snapshot,
782
+ snapshotBlobs: blobs,
783
+ hasAttachmentBlobs: !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
784
+ };
785
+ return JSON.stringify(detachedContainerState);
798
786
  }
799
787
  setAutoReconnectInternal(mode, reason) {
800
788
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
@@ -816,7 +804,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
816
804
  if (this.closed) {
817
805
  throw new telemetry_utils_1.UsageError(`The Container is closed and cannot be connected`);
818
806
  }
819
- else if (this._attachState !== container_definitions_1.AttachState.Attached) {
807
+ else if (this.attachState !== container_definitions_1.AttachState.Attached) {
820
808
  throw new telemetry_utils_1.UsageError(`The Container is not attached and cannot be connected`);
821
809
  }
822
810
  else if (!this.connected) {
@@ -831,7 +819,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
831
819
  }
832
820
  connectInternal(args) {
833
821
  (0, core_utils_1.assert)(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
834
- (0, core_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Attached, 0x2c6 /* "Attempting to connect() a container that is not attached" */);
822
+ (0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x2c6 /* "Attempting to connect() a container that is not attached" */);
835
823
  // Resume processing ops and connect to delta stream
836
824
  this.resumeInternal(args);
837
825
  // Set Auto Reconnect Mode
@@ -935,6 +923,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
935
923
  }
936
924
  this._deltaManager.connect(args);
937
925
  }
926
+ async createDocumentService(serviceProvider) {
927
+ const service = await serviceProvider();
928
+ // Back-compat for Old driver
929
+ if (service.on !== undefined) {
930
+ service.on("metadataUpdate", this.metadataUpdateHandler);
931
+ }
932
+ return service;
933
+ }
938
934
  /**
939
935
  * Load container.
940
936
  *
@@ -942,7 +938,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
942
938
  */
943
939
  async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
944
940
  const timings = { phase1: client_utils_1.performance.now() };
945
- this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
941
+ this.service = await this.createDocumentService(async () => this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType));
946
942
  // Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
947
943
  const mode = this.mc.config.getBoolean("Fluid.Container.ForceWriteConnection") === true ||
948
944
  (pendingLocalState?.savedOps.length ?? 0) > 0
@@ -959,25 +955,37 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
959
955
  this.connectToDeltaStream(connectionArgs);
960
956
  }
961
957
  this.storageAdapter.connectToService(this.service);
962
- this._attachState = container_definitions_1.AttachState.Attached;
958
+ this.attachmentData = {
959
+ state: container_definitions_1.AttachState.Attached,
960
+ };
963
961
  timings.phase2 = client_utils_1.performance.now();
964
962
  // Fetch specified snapshot.
965
963
  const { snapshot, versionId } = pendingLocalState === undefined
966
- ? await this.fetchSnapshotTree(specifiedVersion)
964
+ ? await this.fetchSnapshot(specifiedVersion)
967
965
  : { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
966
+ const snapshotTree = (0, driver_utils_1.isInstanceOfISnapshot)(snapshot)
967
+ ? snapshot.snapshotTree
968
+ : snapshot;
968
969
  if (pendingLocalState) {
969
- this.baseSnapshot = pendingLocalState.baseSnapshot;
970
- this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
970
+ this.attachmentData = {
971
+ state: container_definitions_1.AttachState.Attached,
972
+ snapshot: {
973
+ tree: pendingLocalState.baseSnapshot,
974
+ blobs: pendingLocalState.snapshotBlobs,
975
+ },
976
+ };
971
977
  }
972
978
  else {
973
- (0, core_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
979
+ (0, core_utils_1.assert)(snapshotTree !== undefined, 0x237 /* "Snapshot should exist" */);
974
980
  if (this.offlineLoadEnabled) {
975
- this.baseSnapshot = snapshot;
976
- // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
977
- this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storageAdapter);
981
+ const blobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshotTree, this.storageAdapter);
982
+ this.attachmentData = {
983
+ state: container_definitions_1.AttachState.Attached,
984
+ snapshot: { tree: snapshotTree, blobs },
985
+ };
978
986
  }
979
987
  }
980
- const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
988
+ const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
981
989
  // If we saved ops, we will replay them and don't need DeltaManager to fetch them
982
990
  const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
983
991
  const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
@@ -1042,12 +1050,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1042
1050
  }
1043
1051
  // ...load in the existing quorum
1044
1052
  // Initialize the protocol handler
1045
- await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1053
+ await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshotTree);
1046
1054
  timings.phase3 = client_utils_1.performance.now();
1047
1055
  const codeDetails = this.getCodeDetailsFromQuorum();
1048
- await this.instantiateRuntime(codeDetails, snapshot,
1056
+ await this.instantiateRuntime(codeDetails, snapshotTree,
1049
1057
  // give runtime a dummy value so it knows we're loading from a stash blob
1050
- pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined);
1058
+ pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined, (0, driver_utils_1.isInstanceOfISnapshot)(snapshot) ? snapshot : undefined);
1051
1059
  // replay saved ops
1052
1060
  if (pendingLocalState) {
1053
1061
  for (const message of pendingLocalState.savedOps) {
@@ -1123,18 +1131,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1123
1131
  await this.instantiateRuntime(codeDetails, undefined);
1124
1132
  this.setLoaded();
1125
1133
  }
1126
- async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
1127
- if (detachedContainerSnapshot.tree[hasBlobsSummaryTree] !== undefined) {
1134
+ async rehydrateDetachedFromSnapshot({ attached, baseSnapshot, snapshotBlobs, hasAttachmentBlobs, }) {
1135
+ if (hasAttachmentBlobs) {
1128
1136
  (0, core_utils_1.assert)(!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
1129
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1130
- delete detachedContainerSnapshot.tree[hasBlobsSummaryTree];
1131
1137
  }
1132
- const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
1133
- this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
1134
- const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
1138
+ const snapshotTreeWithBlobContents = (0, utils_1.combineSnapshotTreeAndSnapshotBlobs)(baseSnapshot, snapshotBlobs);
1139
+ this.storageAdapter.loadSnapshotFromSnapshotBlobs(snapshotBlobs);
1140
+ const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTreeWithBlobContents);
1135
1141
  await this.attachDeltaManagerOpHandler(attributes);
1136
1142
  // Initialize the protocol handler
1137
- const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
1143
+ const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTreeWithBlobContents);
1138
1144
  const qValues = await (0, driver_utils_1.readAndParse)(this.storageAdapter, baseTree.blobs.quorumValues);
1139
1145
  this.initializeProtocolState(attributes, {
1140
1146
  members: [],
@@ -1142,7 +1148,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1142
1148
  values: qValues,
1143
1149
  });
1144
1150
  const codeDetails = this.getCodeDetailsFromQuorum();
1145
- await this.instantiateRuntime(codeDetails, snapshotTree);
1151
+ await this.instantiateRuntime(codeDetails, snapshotTreeWithBlobContents);
1146
1152
  this.setLoaded();
1147
1153
  }
1148
1154
  async getDocumentAttributes(storage, tree) {
@@ -1243,7 +1249,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1243
1249
  return pkg;
1244
1250
  }
1245
1251
  static setupClient(containerId, options, clientDetailsOverride) {
1246
- const loaderOptionsClient = structuredClone(options?.client);
1252
+ const loaderOptionsClient = (0, structured_clone_1.default)(options?.client);
1247
1253
  const client = loaderOptionsClient !== undefined
1248
1254
  ? loaderOptionsClient
1249
1255
  : {
@@ -1516,7 +1522,29 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1516
1522
  }
1517
1523
  return { snapshot, versionId: version?.id };
1518
1524
  }
1519
- async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
1525
+ async fetchSnapshot(specifiedVersion) {
1526
+ if (this.mc.config.getBoolean("Fluid.Container.FetchSnapshotUsingGetSnapshotApi") ===
1527
+ true &&
1528
+ this.service?.policies?.supportGetSnapshotApi === true) {
1529
+ const snapshot = await this.storageAdapter.getSnapshot({
1530
+ versionId: specifiedVersion,
1531
+ });
1532
+ const version = {
1533
+ id: snapshot.snapshotTree.id ?? "",
1534
+ treeId: snapshot.snapshotTree.id ?? "",
1535
+ };
1536
+ this._loadedFromVersion = version;
1537
+ if (snapshot === undefined && specifiedVersion !== undefined) {
1538
+ this.mc.logger.sendErrorEvent({
1539
+ eventName: "getSnapshotTreeFailed",
1540
+ id: version.id,
1541
+ });
1542
+ }
1543
+ return { snapshot, versionId: version.id };
1544
+ }
1545
+ return this.fetchSnapshotTree(specifiedVersion);
1546
+ }
1547
+ async instantiateRuntime(codeDetails, snapshotTree, pendingLocalState, snapshot) {
1520
1548
  (0, core_utils_1.assert)(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
1521
1549
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1522
1550
  // are set. Global requests will still go directly to the loader
@@ -1537,8 +1565,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1537
1565
  }
1538
1566
  const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
1539
1567
  this.protocolHandler.quorum.get("code2"));
1540
- const existing = snapshot !== undefined;
1541
- const context = new containerContext_1.ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, this.protocolHandler.audience, loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (content, targetClientId) => this.submitSignal(content, targetClientId), (error) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => this.resolvedUrl?.id, () => this.clientId, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
1568
+ const existing = snapshotTree !== undefined;
1569
+ const context = new containerContext_1.ContainerContext(this.options, this.scope, snapshotTree, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, this.protocolHandler.audience, loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (content, targetClientId) => this.submitSignal(content, targetClientId), (error) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => this.resolvedUrl?.id, () => this.clientId, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState, snapshot);
1542
1570
  this._runtime = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1543
1571
  this._lifecycleEvents.emit("runtimeInstantiated");
1544
1572
  this._loadedCodeDetails = codeDetails;
@@ -1582,4 +1610,4 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1582
1610
  }
1583
1611
  }
1584
1612
  exports.Container = Container;
1585
- //# sourceMappingURL=container.cjs.map
1613
+ //# sourceMappingURL=container.js.map