@fluidframework/container-loader 0.51.3 → 0.53.0-46105

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 (66) hide show
  1. package/dist/connectionStateHandler.d.ts +1 -0
  2. package/dist/connectionStateHandler.d.ts.map +1 -1
  3. package/dist/connectionStateHandler.js +11 -3
  4. package/dist/connectionStateHandler.js.map +1 -1
  5. package/dist/container.d.ts +15 -23
  6. package/dist/container.d.ts.map +1 -1
  7. package/dist/container.js +125 -140
  8. package/dist/container.js.map +1 -1
  9. package/dist/containerContext.d.ts +5 -4
  10. package/dist/containerContext.d.ts.map +1 -1
  11. package/dist/containerContext.js +4 -0
  12. package/dist/containerContext.js.map +1 -1
  13. package/dist/deltaManager.d.ts +0 -7
  14. package/dist/deltaManager.d.ts.map +1 -1
  15. package/dist/deltaManager.js +38 -50
  16. package/dist/deltaManager.js.map +1 -1
  17. package/dist/deltaQueue.d.ts +5 -1
  18. package/dist/deltaQueue.d.ts.map +1 -1
  19. package/dist/deltaQueue.js.map +1 -1
  20. package/dist/loader.d.ts +8 -3
  21. package/dist/loader.d.ts.map +1 -1
  22. package/dist/loader.js +6 -1
  23. package/dist/loader.js.map +1 -1
  24. package/dist/packageVersion.d.ts +1 -1
  25. package/dist/packageVersion.d.ts.map +1 -1
  26. package/dist/packageVersion.js +1 -1
  27. package/dist/packageVersion.js.map +1 -1
  28. package/dist/utils.js +6 -5
  29. package/dist/utils.js.map +1 -1
  30. package/lib/connectionStateHandler.d.ts +1 -0
  31. package/lib/connectionStateHandler.d.ts.map +1 -1
  32. package/lib/connectionStateHandler.js +11 -3
  33. package/lib/connectionStateHandler.js.map +1 -1
  34. package/lib/container.d.ts +15 -23
  35. package/lib/container.d.ts.map +1 -1
  36. package/lib/container.js +127 -142
  37. package/lib/container.js.map +1 -1
  38. package/lib/containerContext.d.ts +5 -4
  39. package/lib/containerContext.d.ts.map +1 -1
  40. package/lib/containerContext.js +4 -0
  41. package/lib/containerContext.js.map +1 -1
  42. package/lib/deltaManager.d.ts +0 -7
  43. package/lib/deltaManager.d.ts.map +1 -1
  44. package/lib/deltaManager.js +38 -50
  45. package/lib/deltaManager.js.map +1 -1
  46. package/lib/deltaQueue.d.ts +5 -1
  47. package/lib/deltaQueue.d.ts.map +1 -1
  48. package/lib/deltaQueue.js.map +1 -1
  49. package/lib/loader.d.ts +8 -3
  50. package/lib/loader.d.ts.map +1 -1
  51. package/lib/loader.js +6 -1
  52. package/lib/loader.js.map +1 -1
  53. package/lib/packageVersion.d.ts +1 -1
  54. package/lib/packageVersion.d.ts.map +1 -1
  55. package/lib/packageVersion.js +1 -1
  56. package/lib/packageVersion.js.map +1 -1
  57. package/lib/utils.js +6 -5
  58. package/lib/utils.js.map +1 -1
  59. package/package.json +11 -11
  60. package/src/connectionStateHandler.ts +14 -4
  61. package/src/container.ts +150 -160
  62. package/src/containerContext.ts +8 -3
  63. package/src/deltaManager.ts +50 -56
  64. package/src/deltaQueue.ts +10 -1
  65. package/src/loader.ts +33 -24
  66. package/src/packageVersion.ts +1 -1
package/src/container.ts CHANGED
@@ -51,7 +51,7 @@ import {
51
51
  ensureFluidResolvedUrl,
52
52
  combineAppAndProtocolSummary,
53
53
  runWithRetry,
54
- canRetryOnError,
54
+ isFluidResolvedUrl,
55
55
  } from "@fluidframework/driver-utils";
56
56
  import {
57
57
  isSystemMessage,
@@ -175,7 +175,7 @@ export async function waitContainerToCatchUp(container: Container) {
175
175
  throw new Error("Container is closed");
176
176
  }
177
177
 
178
- return new Promise<boolean>((accept, reject) => {
178
+ return new Promise<boolean>((resolve, reject) => {
179
179
  const deltaManager = container.deltaManager;
180
180
 
181
181
  container.on("closed", reject);
@@ -189,12 +189,12 @@ export async function waitContainerToCatchUp(container: Container) {
189
189
  assert(deltaManager.lastSequenceNumber <= connectionOpSeqNumber,
190
190
  0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
191
191
  if (deltaManager.lastSequenceNumber === connectionOpSeqNumber) {
192
- accept(hasCheckpointSequenceNumber);
192
+ resolve(hasCheckpointSequenceNumber);
193
193
  return;
194
194
  }
195
195
  const callbackOps = (message: ISequencedDocumentMessage) => {
196
196
  if (connectionOpSeqNumber <= message.sequenceNumber) {
197
- accept(hasCheckpointSequenceNumber);
197
+ resolve(hasCheckpointSequenceNumber);
198
198
  deltaManager.off("op", callbackOps);
199
199
  }
200
200
  };
@@ -279,7 +279,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
279
279
  onClosed(err);
280
280
  });
281
281
  }),
282
- { start: true, end: true, cancel: "error" },
282
+ { start: true, end: true, cancel: "generic" },
283
283
  );
284
284
  }
285
285
 
@@ -293,9 +293,16 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
293
293
  const container = new Container(
294
294
  loader,
295
295
  {});
296
- container._lifecycleState = "loading";
297
- await container.createDetached(codeDetails);
298
- return container;
296
+
297
+ return PerformanceEvent.timedExecAsync(
298
+ container.logger,
299
+ { eventName: "CreateDetached" },
300
+ async (_event) => {
301
+ container._lifecycleState = "loading";
302
+ await container.createDetached(codeDetails);
303
+ return container;
304
+ },
305
+ { start: true, end: true, cancel: "generic" });
299
306
  }
300
307
 
301
308
  /**
@@ -309,10 +316,16 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
309
316
  const container = new Container(
310
317
  loader,
311
318
  {});
312
- const deserializedSummary = JSON.parse(snapshot) as ISummaryTree;
313
- container._lifecycleState = "loading";
314
- await container.rehydrateDetachedFromSnapshot(deserializedSummary);
315
- return container;
319
+ return PerformanceEvent.timedExecAsync(
320
+ container.logger,
321
+ { eventName: "RehydrateDetachedFromSnapshot" },
322
+ async (_event) => {
323
+ const deserializedSummary = JSON.parse(snapshot) as ISummaryTree;
324
+ container._lifecycleState = "loading";
325
+ await container.rehydrateDetachedFromSnapshot(deserializedSummary);
326
+ return container;
327
+ },
328
+ { start: true, end: true, cancel: "generic" });
316
329
  }
317
330
 
318
331
  public subLogger: TelemetryLogger;
@@ -412,33 +425,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
412
425
  return this._loadedFromVersion;
413
426
  }
414
427
 
415
- /**
416
- * Tells if container is in read-only mode.
417
- * Data stores should listen for "readonly" notifications and disallow user making changes to data stores.
418
- * Readonly state can be because of no storage write permission,
419
- * or due to host forcing readonly mode for container.
420
- *
421
- * We do not differentiate here between no write access to storage vs. host disallowing changes to container -
422
- * in all cases container runtime and data stores should respect readonly state and not allow local changes.
423
- *
424
- * It is undefined if we have not yet established websocket connection
425
- * and do not know if user has write access to a file.
426
- * @deprecated - use readOnlyInfo
427
- */
428
- public get readonly() {
429
- return this._deltaManager.readonly;
430
- }
431
-
432
- /**
433
- * Tells if user has no write permissions for file in storage
434
- * It is undefined if we have not yet established websocket connection
435
- * and do not know if user has write access to a file.
436
- * @deprecated - use readOnlyInfo
437
- */
438
- public get readonlyPermissions() {
439
- return this._deltaManager.readonlyPermissions;
440
- }
441
-
442
428
  public get readOnlyInfo(): ReadOnlyInfo {
443
429
  return this._deltaManager.readOnlyInfo;
444
430
  }
@@ -495,16 +481,32 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
495
481
  }
496
482
 
497
483
  /**
498
- * @deprecated use codeDetails
484
+ * The current code details for the container's runtime
485
+ * @deprecated use getSpecifiedCodeDetails for the code details currently specified for this container, or
486
+ * getLoadedCodeDetails for the code details that the container's context was loaded with.
487
+ * To be removed after getSpecifiedCodeDetails and getLoadedCodeDetails become ubiquitous.
499
488
  */
500
- public get chaincodePackage(): IFluidCodeDetails | undefined {
501
- return this.codeDetails;
502
- }
503
-
504
489
  public get codeDetails(): IFluidCodeDetails | undefined {
505
490
  return this._context?.codeDetails ?? this.getCodeDetailsFromQuorum();
506
491
  }
507
492
 
493
+ /**
494
+ * Get the code details that are currently specified for the container.
495
+ * @returns The current code details if any are specified, undefined if none are specified.
496
+ */
497
+ public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
498
+ return this.getCodeDetailsFromQuorum();
499
+ }
500
+
501
+ /**
502
+ * Get the code details that were used to load the container.
503
+ * @returns The code details that were used to load the container if it is loaded, undefined if it is not yet
504
+ * loaded.
505
+ */
506
+ public getLoadedCodeDetails(): IFluidCodeDetails | undefined {
507
+ return this._context?.codeDetails;
508
+ }
509
+
508
510
  /**
509
511
  * Retrieves the audience associated with the document
510
512
  */
@@ -560,7 +562,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
560
562
  {
561
563
  all: {
562
564
  clientType, // Differentiating summarizer container from main container
563
- loaderVersion: pkgVersion,
564
565
  containerId: uuid(),
565
566
  docId: () => this.id,
566
567
  containerAttachState: () => this._attachState,
@@ -656,22 +657,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
656
657
  switch (event) {
657
658
  case dirtyContainerEvent:
658
659
  if (this._dirtyContainer) {
659
- listener(this._dirtyContainer);
660
+ listener();
660
661
  }
661
662
  break;
662
663
  case savedContainerEvent:
663
664
  if (!this._dirtyContainer) {
664
- listener(this._dirtyContainer);
665
+ listener();
665
666
  }
666
667
  break;
667
668
  case connectedEventName:
668
669
  if (this.connected) {
669
- listener(event, this.clientId);
670
+ listener(this.clientId);
670
671
  }
671
672
  break;
672
673
  case disconnectedEventName:
673
674
  if (!this.connected) {
674
- listener(event);
675
+ listener();
675
676
  }
676
677
  break;
677
678
  default:
@@ -703,6 +704,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
703
704
 
704
705
  this._protocolHandler?.close();
705
706
 
707
+ this.connectionStateHandler.dispose();
708
+
706
709
  this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
707
710
 
708
711
  assert(this.connectionState === ConnectionState.Disconnected,
@@ -770,114 +773,113 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
770
773
  }
771
774
 
772
775
  public async attach(request: IRequest): Promise<void> {
773
- if (this._lifecycleState !== "loaded") {
774
- throw new UsageError(`containerNotValidForAttach [${this._lifecycleState}]`);
775
- }
776
+ await PerformanceEvent.timedExecAsync(this.logger, { eventName: "Attach" }, async () => {
777
+ if (this._lifecycleState !== "loaded") {
778
+ throw new UsageError(`containerNotValidForAttach [${this._lifecycleState}]`);
779
+ }
776
780
 
777
- // If container is already attached or attach is in progress, throw an error.
778
- assert(this._attachState === AttachState.Detached && !this.attachStarted,
779
- 0x205 /* "attach() called more than once" */);
780
- this.attachStarted = true;
781
+ // If container is already attached or attach is in progress, throw an error.
782
+ assert(this._attachState === AttachState.Detached && !this.attachStarted,
783
+ 0x205 /* "attach() called more than once" */);
784
+ this.attachStarted = true;
781
785
 
782
- // If attachment blobs were uploaded in detached state we will go through a different attach flow
783
- const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
784
- && this.loader.services.detachedBlobStorage.size > 0;
786
+ // If attachment blobs were uploaded in detached state we will go through a different attach flow
787
+ const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
788
+ && this.loader.services.detachedBlobStorage.size > 0;
785
789
 
786
- try {
787
- assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
788
-
789
- let summary: ISummaryTree;
790
- if (!hasAttachmentBlobs) {
791
- // Get the document state post attach - possibly can just call attach but we need to change the
792
- // semantics around what the attach means as far as async code goes.
793
- const appSummary: ISummaryTree = this.context.createSummary();
794
- const protocolSummary = this.captureProtocolSummary();
795
- summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
796
-
797
- // Set the state as attaching as we are starting the process of attaching container.
798
- // This should be fired after taking the summary because it is the place where we are
799
- // starting to attach the container to storage.
800
- // Also, this should only be fired in detached container.
801
- this._attachState = AttachState.Attaching;
802
- this.context.notifyAttaching();
803
- }
790
+ try {
791
+ assert(this.deltaManager.inbound.length === 0,
792
+ 0x0d6 /* "Inbound queue should be empty when attaching" */);
793
+
794
+ let summary: ISummaryTree;
795
+ if (!hasAttachmentBlobs) {
796
+ // Get the document state post attach - possibly can just call attach but we need to change the
797
+ // semantics around what the attach means as far as async code goes.
798
+ const appSummary: ISummaryTree = this.context.createSummary();
799
+ const protocolSummary = this.captureProtocolSummary();
800
+ summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
801
+
802
+ // Set the state as attaching as we are starting the process of attaching container.
803
+ // This should be fired after taking the summary because it is the place where we are
804
+ // starting to attach the container to storage.
805
+ // Also, this should only be fired in detached container.
806
+ this._attachState = AttachState.Attaching;
807
+ this.context.notifyAttaching();
808
+ }
804
809
 
805
- // Actually go and create the resolved document
806
- const createNewResolvedUrl = await this.urlResolver.resolve(request);
807
- ensureFluidResolvedUrl(createNewResolvedUrl);
808
- if (this.service === undefined) {
809
- this.service = await runWithRetry(
810
- async () => this.serviceFactory.createContainer(
811
- summary,
812
- createNewResolvedUrl,
813
- this.subLogger,
814
- ),
815
- "containerAttach",
816
- this.logger,
817
- {}, // progress
818
- );
819
- }
820
- const resolvedUrl = this.service.resolvedUrl;
821
- ensureFluidResolvedUrl(resolvedUrl);
822
- this._resolvedUrl = resolvedUrl;
823
- await this.connectStorageService();
824
-
825
- if (hasAttachmentBlobs) {
826
- // upload blobs to storage
827
- assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
828
-
829
- // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
830
- // support blob handles that only know about the local IDs
831
- const redirectTable = new Map<string, string>();
832
- // if new blobs are added while uploading, upload them too
833
- while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
834
- const newIds = this.loader.services.detachedBlobStorage.getBlobIds().filter(
835
- (id) => !redirectTable.has(id));
836
- for (const id of newIds) {
837
- const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
838
- const response = await this.storageService.createBlob(blob);
839
- redirectTable.set(id, response.id);
840
- }
810
+ // Actually go and create the resolved document
811
+ const createNewResolvedUrl = await this.urlResolver.resolve(request);
812
+ ensureFluidResolvedUrl(createNewResolvedUrl);
813
+ if (this.service === undefined) {
814
+ this.service = await runWithRetry(
815
+ async () => this.serviceFactory.createContainer(
816
+ summary,
817
+ createNewResolvedUrl,
818
+ this.subLogger,
819
+ ),
820
+ "containerAttach",
821
+ this.logger,
822
+ {}, // progress
823
+ );
841
824
  }
825
+ const resolvedUrl = this.service.resolvedUrl;
826
+ ensureFluidResolvedUrl(resolvedUrl);
827
+ this._resolvedUrl = resolvedUrl;
828
+ await this.connectStorageService();
829
+
830
+ if (hasAttachmentBlobs) {
831
+ // upload blobs to storage
832
+ assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
833
+
834
+ // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
835
+ // support blob handles that only know about the local IDs
836
+ const redirectTable = new Map<string, string>();
837
+ // if new blobs are added while uploading, upload them too
838
+ while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
839
+ const newIds = this.loader.services.detachedBlobStorage.getBlobIds().filter(
840
+ (id) => !redirectTable.has(id));
841
+ for (const id of newIds) {
842
+ const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
843
+ const response = await this.storageService.createBlob(blob);
844
+ redirectTable.set(id, response.id);
845
+ }
846
+ }
842
847
 
843
- // take summary and upload
844
- const appSummary: ISummaryTree = this.context.createSummary(redirectTable);
845
- const protocolSummary = this.captureProtocolSummary();
846
- summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
848
+ // take summary and upload
849
+ const appSummary: ISummaryTree = this.context.createSummary(redirectTable);
850
+ const protocolSummary = this.captureProtocolSummary();
851
+ summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
847
852
 
848
- this._attachState = AttachState.Attaching;
849
- this.context.notifyAttaching();
853
+ this._attachState = AttachState.Attaching;
854
+ this.context.notifyAttaching();
850
855
 
851
- await this.storageService.uploadSummaryWithContext(summary, {
852
- referenceSequenceNumber: 0,
853
- ackHandle: undefined,
854
- proposalHandle: undefined,
855
- });
856
- }
856
+ await this.storageService.uploadSummaryWithContext(summary, {
857
+ referenceSequenceNumber: 0,
858
+ ackHandle: undefined,
859
+ proposalHandle: undefined,
860
+ });
861
+ }
857
862
 
858
- this._attachState = AttachState.Attached;
859
- this.emit("attached");
863
+ this._attachState = AttachState.Attached;
864
+ this.emit("attached");
860
865
 
861
- // Propagate current connection state through the system.
862
- this.propagateConnectionState();
863
- if (!this.closed) {
864
- this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
865
- }
866
- } catch(error) {
867
- // we should retry upon any retriable errors, so we shouldn't see them here
868
- assert(!canRetryOnError(error), 0x24f /* "retriable error thrown from attach()" */);
869
-
870
- // add resolved URL on error object so that host has the ability to find this document and delete it
871
- const newError = normalizeError(error);
872
- const resolvedUrl = this.resolvedUrl;
873
- if (resolvedUrl) {
874
- ensureFluidResolvedUrl(resolvedUrl);
875
- newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
866
+ // Propagate current connection state through the system.
867
+ this.propagateConnectionState();
868
+ if (!this.closed) {
869
+ this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
870
+ }
871
+ } catch(error) {
872
+ // add resolved URL on error object so that host has the ability to find this document and delete it
873
+ const newError = normalizeError(error);
874
+ const resolvedUrl = this.resolvedUrl;
875
+ if (isFluidResolvedUrl(resolvedUrl)) {
876
+ newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
877
+ }
878
+ this.close(newError);
879
+ throw newError;
876
880
  }
877
- this.close(newError);
878
- // eslint-disable-next-line @typescript-eslint/no-throw-literal
879
- throw newError;
880
- }
881
+ },
882
+ { start: true, end: true, cancel: "generic" });
881
883
  }
882
884
 
883
885
  public async request(path: IRequest): Promise<IResponse> {
@@ -1269,7 +1271,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1269
1271
 
1270
1272
  private async createDetached(source: IFluidCodeDetails) {
1271
1273
  const attributes: IDocumentAttributes = {
1272
- branch: "",
1273
1274
  sequenceNumber: detachedContainerRefSeqNumber,
1274
1275
  term: 1,
1275
1276
  minimumSequenceNumber: 0,
@@ -1362,7 +1363,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1362
1363
  ): Promise<IDocumentAttributes> {
1363
1364
  if (tree === undefined) {
1364
1365
  return {
1365
- branch: this.id,
1366
1366
  minimumSequenceNumber: 0,
1367
1367
  sequenceNumber: 0,
1368
1368
  term: 1,
@@ -1472,7 +1472,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1472
1472
 
1473
1473
  // Save attributes for the document
1474
1474
  const documentAttributes: IDocumentAttributes = {
1475
- branch: this.id,
1476
1475
  minimumSequenceNumber: this.protocolHandler.minimumSequenceNumber,
1477
1476
  sequenceNumber: this.protocolHandler.sequenceNumber,
1478
1477
  term: this.protocolHandler.term,
@@ -1527,7 +1526,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1527
1526
  if (this.clientDetailsOverride !== undefined) {
1528
1527
  merge(client.details, this.clientDetailsOverride);
1529
1528
  }
1530
-
1529
+ client.details.environment = [client.details.environment, ` loaderVersion:${pkgVersion}`].join(";");
1531
1530
  return client;
1532
1531
  }
1533
1532
 
@@ -1538,17 +1537,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1538
1537
  * If it's not true, runtime is not in position to send ops.
1539
1538
  */
1540
1539
  private activeConnection() {
1541
- const active = this.connectionState === ConnectionState.Connected &&
1540
+ return this.connectionState === ConnectionState.Connected &&
1542
1541
  this._deltaManager.connectionMode === "write";
1543
-
1544
- // Check for presence of current client in quorum for "write" connections - inactive clients
1545
- // would get leave op after some long timeout (5 min) and that should automatically transition
1546
- // state to "read" mode.
1547
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1548
- assert(!active || this.getQuorum().getMember(this.clientId!) !== undefined,
1549
- 0x276 /* "active connection not present in quorum" */);
1550
-
1551
- return active;
1552
1542
  }
1553
1543
 
1554
1544
  private createDeltaManager() {
@@ -12,6 +12,7 @@ import {
12
12
  IFluidCodeDetails,
13
13
  IFluidCodeDetailsComparer,
14
14
  IProvideFluidCodeDetailsComparer,
15
+ FluidObject,
15
16
  } from "@fluidframework/core-interfaces";
16
17
  import {
17
18
  IAudience,
@@ -51,7 +52,7 @@ const PackageNotFactoryError = "Code package does not implement IRuntimeFactory"
51
52
  export class ContainerContext implements IContainerContext {
52
53
  public static async createOrLoad(
53
54
  container: Container,
54
- scope: IFluidObject,
55
+ scope: FluidObject,
55
56
  codeLoader: ICodeDetailsLoader | ICodeLoader,
56
57
  codeDetails: IFluidCodeDetails,
57
58
  baseSnapshot: ISnapshotTree | undefined,
@@ -166,7 +167,7 @@ export class ContainerContext implements IContainerContext {
166
167
 
167
168
  constructor(
168
169
  private readonly container: Container,
169
- public readonly scope: IFluidObject,
170
+ public readonly scope: IFluidObject & FluidObject,
170
171
  private readonly codeLoader: ICodeDetailsLoader | ICodeLoader,
171
172
  private readonly _codeDetails: IFluidCodeDetails,
172
173
  private readonly _baseSnapshot: ISnapshotTree | undefined,
@@ -190,6 +191,10 @@ export class ContainerContext implements IContainerContext {
190
191
  this.attachListener();
191
192
  }
192
193
 
194
+ public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
195
+ return (this.quorum.get("code") ?? this.quorum.get("code2")) as IFluidCodeDetails | undefined;
196
+ }
197
+
193
198
  public dispose(error?: Error): void {
194
199
  if (this._disposed) {
195
200
  return;
@@ -299,7 +304,7 @@ export class ContainerContext implements IContainerContext {
299
304
  // #region private
300
305
 
301
306
  private async getRuntimeFactory(): Promise<IRuntimeFactory> {
302
- const fluidExport: Partial<IProvideRuntimeFactory> | undefined =
307
+ const fluidExport: FluidObject<IProvideRuntimeFactory> | undefined =
303
308
  (await this._fluidModuleP).module?.fluidExport;
304
309
  const runtimeFactory = fluidExport?.IRuntimeFactory;
305
310
  if (runtimeFactory === undefined) {