@fluidframework/container-loader 2.0.0-internal.5.0.1 → 2.0.0-internal.5.1.0

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 (86) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +21 -0
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +32 -13
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionStateHandler.d.ts +11 -0
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +24 -1
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts +15 -11
  11. package/dist/container.d.ts.map +1 -1
  12. package/dist/container.js +44 -55
  13. package/dist/container.js.map +1 -1
  14. package/dist/containerStorageAdapter.d.ts.map +1 -1
  15. package/dist/containerStorageAdapter.js +6 -15
  16. package/dist/containerStorageAdapter.js.map +1 -1
  17. package/dist/contracts.d.ts +8 -0
  18. package/dist/contracts.d.ts.map +1 -1
  19. package/dist/contracts.js.map +1 -1
  20. package/dist/deltaManager.d.ts +18 -7
  21. package/dist/deltaManager.d.ts.map +1 -1
  22. package/dist/deltaManager.js +42 -30
  23. package/dist/deltaManager.js.map +1 -1
  24. package/dist/deltaQueue.d.ts +2 -3
  25. package/dist/deltaQueue.d.ts.map +1 -1
  26. package/dist/deltaQueue.js +2 -3
  27. package/dist/deltaQueue.js.map +1 -1
  28. package/dist/loader.d.ts +0 -2
  29. package/dist/loader.d.ts.map +1 -1
  30. package/dist/loader.js +24 -32
  31. package/dist/loader.js.map +1 -1
  32. package/dist/packageVersion.d.ts +1 -1
  33. package/dist/packageVersion.js +1 -1
  34. package/dist/packageVersion.js.map +1 -1
  35. package/dist/tsdoc-metadata.json +11 -0
  36. package/dist/utils.d.ts +2 -0
  37. package/dist/utils.d.ts.map +1 -1
  38. package/dist/utils.js +8 -1
  39. package/dist/utils.js.map +1 -1
  40. package/lib/connectionManager.d.ts.map +1 -1
  41. package/lib/connectionManager.js +32 -13
  42. package/lib/connectionManager.js.map +1 -1
  43. package/lib/connectionStateHandler.d.ts +11 -0
  44. package/lib/connectionStateHandler.d.ts.map +1 -1
  45. package/lib/connectionStateHandler.js +24 -1
  46. package/lib/connectionStateHandler.js.map +1 -1
  47. package/lib/container.d.ts +15 -11
  48. package/lib/container.d.ts.map +1 -1
  49. package/lib/container.js +44 -55
  50. package/lib/container.js.map +1 -1
  51. package/lib/containerStorageAdapter.d.ts.map +1 -1
  52. package/lib/containerStorageAdapter.js +6 -15
  53. package/lib/containerStorageAdapter.js.map +1 -1
  54. package/lib/contracts.d.ts +8 -0
  55. package/lib/contracts.d.ts.map +1 -1
  56. package/lib/contracts.js.map +1 -1
  57. package/lib/deltaManager.d.ts +18 -7
  58. package/lib/deltaManager.d.ts.map +1 -1
  59. package/lib/deltaManager.js +44 -32
  60. package/lib/deltaManager.js.map +1 -1
  61. package/lib/deltaQueue.d.ts +2 -3
  62. package/lib/deltaQueue.d.ts.map +1 -1
  63. package/lib/deltaQueue.js +2 -3
  64. package/lib/deltaQueue.js.map +1 -1
  65. package/lib/loader.d.ts +0 -2
  66. package/lib/loader.d.ts.map +1 -1
  67. package/lib/loader.js +24 -32
  68. package/lib/loader.js.map +1 -1
  69. package/lib/packageVersion.d.ts +1 -1
  70. package/lib/packageVersion.js +1 -1
  71. package/lib/packageVersion.js.map +1 -1
  72. package/lib/utils.d.ts +2 -0
  73. package/lib/utils.d.ts.map +1 -1
  74. package/lib/utils.js +7 -1
  75. package/lib/utils.js.map +1 -1
  76. package/package.json +17 -36
  77. package/src/connectionManager.ts +33 -15
  78. package/src/connectionStateHandler.ts +45 -1
  79. package/src/container.ts +87 -75
  80. package/src/containerStorageAdapter.ts +4 -16
  81. package/src/contracts.ts +10 -0
  82. package/src/deltaManager.ts +52 -33
  83. package/src/deltaQueue.ts +2 -3
  84. package/src/loader.ts +33 -40
  85. package/src/packageVersion.ts +1 -1
  86. package/src/utils.ts +15 -1
package/src/container.ts CHANGED
@@ -342,17 +342,6 @@ export class Container
342
342
  {
343
343
  public static version = "^0.1.0";
344
344
 
345
- public static async clone(
346
- container: Container,
347
- loadProps: IContainerLoadProps,
348
- createParamOverrides: Partial<IContainerCreateProps>,
349
- ) {
350
- return this.load(loadProps, {
351
- ...container.createProps,
352
- ...createParamOverrides,
353
- });
354
- }
355
-
356
345
  /**
357
346
  * Load an existing container.
358
347
  * @internal
@@ -451,14 +440,30 @@ export class Container
451
440
  );
452
441
  }
453
442
 
454
- public subLogger: TelemetryLogger;
455
-
456
443
  // Tells if container can reconnect on losing fist connection
457
444
  // If false, container gets closed on loss of connection.
458
- private readonly _canReconnect: boolean = true;
445
+ private readonly _canReconnect: boolean;
446
+ private readonly clientDetailsOverride: IClientDetails | undefined;
447
+ private readonly urlResolver: IUrlResolver;
448
+ private readonly serviceFactory: IDocumentServiceFactory;
449
+ private readonly codeLoader: ICodeDetailsLoader;
450
+ public readonly options: ILoaderOptions;
451
+ private readonly scope: FluidObject;
452
+ public subLogger: TelemetryLogger;
453
+ private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
454
+ private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
459
455
 
460
456
  private readonly mc: MonitoringContext;
461
457
 
458
+ /**
459
+ * Used by the RelativeLoader to spawn a new Container for the same document. Used to create the summarizing client.
460
+ * @internal
461
+ */
462
+ public readonly clone: (
463
+ loadProps: IContainerLoadProps,
464
+ createParamOverrides: Partial<IContainerCreateProps>,
465
+ ) => Promise<Container>;
466
+
462
467
  /**
463
468
  * Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
464
469
  *
@@ -508,7 +513,6 @@ export class Container
508
513
  return this.storageAdapter;
509
514
  }
510
515
 
511
- private readonly clientDetailsOverride: IClientDetails | undefined;
512
516
  private readonly _deltaManager: DeltaManager<ConnectionManager>;
513
517
  private service: IDocumentService | undefined;
514
518
 
@@ -672,20 +676,6 @@ export class Container
672
676
  return this._dirtyContainer;
673
677
  }
674
678
 
675
- private get serviceFactory() {
676
- return this.createProps.documentServiceFactory;
677
- }
678
- private get urlResolver() {
679
- return this.createProps.urlResolver;
680
- }
681
- public readonly options: ILoaderOptions;
682
- private get scope() {
683
- return this.createProps.scope;
684
- }
685
- private get codeLoader() {
686
- return this.createProps.codeLoader;
687
- }
688
-
689
679
  /**
690
680
  * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
691
681
  */
@@ -725,8 +715,8 @@ export class Container
725
715
  * @internal
726
716
  */
727
717
  constructor(
728
- private readonly createProps: IContainerCreateProps,
729
- loadProps?: IContainerLoadProps,
718
+ createProps: IContainerCreateProps,
719
+ loadProps?: Pick<IContainerLoadProps, "pendingLocalState">,
730
720
  ) {
731
721
  super((name, error) => {
732
722
  this.mc.logger.sendErrorEvent(
@@ -738,10 +728,46 @@ export class Container
738
728
  );
739
729
  });
740
730
 
741
- this.clientDetailsOverride = createProps.clientDetailsOverride;
742
- if (createProps.canReconnect !== undefined) {
743
- this._canReconnect = createProps.canReconnect;
744
- }
731
+ const {
732
+ canReconnect,
733
+ clientDetailsOverride,
734
+ urlResolver,
735
+ documentServiceFactory,
736
+ codeLoader,
737
+ options,
738
+ scope,
739
+ subLogger,
740
+ detachedBlobStorage,
741
+ protocolHandlerBuilder,
742
+ } = createProps;
743
+
744
+ this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
745
+ const pendingLocalState = loadProps?.pendingLocalState;
746
+
747
+ this._canReconnect = canReconnect ?? true;
748
+ this.clientDetailsOverride = clientDetailsOverride;
749
+ this.urlResolver = urlResolver;
750
+ this.serviceFactory = documentServiceFactory;
751
+ this.codeLoader = codeLoader;
752
+ // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
753
+ // all clients that were loaded from the same loader (including summarizer clients).
754
+ // Tracking alternative ways to handle this in AB#4129.
755
+ this.options = { ...options };
756
+ this.scope = scope;
757
+ this.detachedBlobStorage = detachedBlobStorage;
758
+ this.protocolHandlerBuilder =
759
+ protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
760
+
761
+ // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
762
+ this.clone = async (
763
+ _loadProps: IContainerLoadProps,
764
+ createParamOverrides: Partial<IContainerCreateProps>,
765
+ ) => {
766
+ return Container.load(_loadProps, {
767
+ ...createProps,
768
+ ...createParamOverrides,
769
+ });
770
+ };
745
771
 
746
772
  // Create logger for data stores to use
747
773
  const type = this.client.details.type;
@@ -751,7 +777,7 @@ export class Container
751
777
  }`;
752
778
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
753
779
  // We assign the id later so property getter is used.
754
- this.subLogger = ChildLogger.create(createProps.subLogger, undefined, {
780
+ this.subLogger = ChildLogger.create(subLogger, undefined, {
755
781
  all: {
756
782
  clientType, // Differentiating summarizer container from main container
757
783
  containerId: uuid(),
@@ -759,7 +785,7 @@ export class Container
759
785
  containerAttachState: () => this._attachState,
760
786
  containerLifecycleState: () => this._lifecycleState,
761
787
  containerConnectionState: () => ConnectionState[this.connectionState],
762
- serializedContainer: loadProps?.pendingLocalState !== undefined,
788
+ serializedContainer: pendingLocalState !== undefined,
763
789
  },
764
790
  // we need to be judicious with our logging here to avoid generating too much data
765
791
  // all data logged here should be broadly applicable, and not specific to a
@@ -785,13 +811,6 @@ export class Container
785
811
  // Prefix all events in this file with container-loader
786
812
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
787
813
 
788
- // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
789
- // all clients that were loaded from the same loader (including summarizer clients).
790
- // Tracking alternative ways to handle this in AB#4129.
791
- this.options = {
792
- ...this.createProps.options,
793
- };
794
-
795
814
  this._deltaManager = this.createDeltaManager();
796
815
 
797
816
  this.connectionStateHandler = createConnectionStateHandler(
@@ -812,7 +831,7 @@ export class Container
812
831
  }
813
832
  },
814
833
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
815
- maxClientLeaveWaitTime: this.createProps.options.maxClientLeaveWaitTime,
834
+ maxClientLeaveWaitTime: options.maxClientLeaveWaitTime,
816
835
  logConnectionIssue: (
817
836
  eventName: string,
818
837
  category: TelemetryEventCategory,
@@ -849,7 +868,7 @@ export class Container
849
868
  },
850
869
  },
851
870
  this.deltaManager,
852
- loadProps?.pendingLocalState?.clientId,
871
+ pendingLocalState?.clientId,
853
872
  );
854
873
 
855
874
  this.on(savedContainerEvent, () => {
@@ -868,12 +887,12 @@ export class Container
868
887
  // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
869
888
  const forceEnableSummarizeProtocolTree =
870
889
  this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
871
- this.createProps.options.summarizeProtocolTree;
890
+ options.summarizeProtocolTree;
872
891
 
873
892
  this.storageAdapter = new ContainerStorageAdapter(
874
- this.createProps.detachedBlobStorage,
893
+ detachedBlobStorage,
875
894
  this.mc.logger,
876
- loadProps?.pendingLocalState?.snapshotBlobs,
895
+ pendingLocalState?.snapshotBlobs,
877
896
  addProtocolSummaryIfMissing,
878
897
  forceEnableSummarizeProtocolTree,
879
898
  );
@@ -908,7 +927,7 @@ export class Container
908
927
  }
909
928
 
910
929
  public dispose(error?: ICriticalContainerError) {
911
- this._deltaManager.close(error, true /* doDispose */);
930
+ this._deltaManager.dispose(error);
912
931
  this.verifyClosed();
913
932
  }
914
933
 
@@ -1078,7 +1097,7 @@ export class Container
1078
1097
  const protocolSummary = this.captureProtocolSummary();
1079
1098
  const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1080
1099
 
1081
- if (this.createProps.detachedBlobStorage && this.createProps.detachedBlobStorage.size > 0) {
1100
+ if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
1082
1101
  combinedSummary.tree[".hasAttachmentBlobs"] = {
1083
1102
  type: SummaryType.Blob,
1084
1103
  content: "true",
@@ -1108,8 +1127,7 @@ export class Container
1108
1127
 
1109
1128
  // If attachment blobs were uploaded in detached state we will go through a different attach flow
1110
1129
  const hasAttachmentBlobs =
1111
- this.createProps.detachedBlobStorage !== undefined &&
1112
- this.createProps.detachedBlobStorage.size > 0;
1130
+ this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
1113
1131
 
1114
1132
  try {
1115
1133
  assert(
@@ -1167,7 +1185,7 @@ export class Container
1167
1185
  if (hasAttachmentBlobs) {
1168
1186
  // upload blobs to storage
1169
1187
  assert(
1170
- !!this.createProps.detachedBlobStorage,
1188
+ !!this.detachedBlobStorage,
1171
1189
  0x24e /* "assertion for type narrowing" */,
1172
1190
  );
1173
1191
 
@@ -1175,14 +1193,12 @@ export class Container
1175
1193
  // support blob handles that only know about the local IDs
1176
1194
  const redirectTable = new Map<string, string>();
1177
1195
  // if new blobs are added while uploading, upload them too
1178
- while (redirectTable.size < this.createProps.detachedBlobStorage.size) {
1179
- const newIds = this.createProps.detachedBlobStorage
1196
+ while (redirectTable.size < this.detachedBlobStorage.size) {
1197
+ const newIds = this.detachedBlobStorage
1180
1198
  .getBlobIds()
1181
1199
  .filter((id) => !redirectTable.has(id));
1182
1200
  for (const id of newIds) {
1183
- const blob = await this.createProps.detachedBlobStorage.readBlob(
1184
- id,
1185
- );
1201
+ const blob = await this.detachedBlobStorage.readBlob(id);
1186
1202
  const response = await this.storageAdapter.createBlob(blob);
1187
1203
  redirectTable.set(id, response.id);
1188
1204
  }
@@ -1376,15 +1392,7 @@ export class Container
1376
1392
  return versions[0];
1377
1393
  }
1378
1394
 
1379
- private recordConnectStartTime() {
1380
- if (this.connectionTransitionTimes[ConnectionState.Disconnected] === undefined) {
1381
- this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
1382
- }
1383
- }
1384
-
1385
1395
  private connectToDeltaStream(args: IConnectionArgs) {
1386
- this.recordConnectStartTime();
1387
-
1388
1396
  // All agents need "write" access, including summarizer.
1389
1397
  if (!this._canReconnect || !this.client.details.capabilities.interactive) {
1390
1398
  args.mode = "write";
@@ -1619,8 +1627,7 @@ export class Container
1619
1627
  private async rehydrateDetachedFromSnapshot(detachedContainerSnapshot: ISummaryTree) {
1620
1628
  if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
1621
1629
  assert(
1622
- !!this.createProps.detachedBlobStorage &&
1623
- this.createProps.detachedBlobStorage.size > 0,
1630
+ !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1624
1631
  0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
1625
1632
  );
1626
1633
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
@@ -1717,10 +1724,7 @@ export class Container
1717
1724
  attributes: IDocumentAttributes,
1718
1725
  quorumSnapshot: IQuorumSnapshot,
1719
1726
  ): void {
1720
- const protocolHandlerBuilder =
1721
- this.createProps.protocolHandlerBuilder ??
1722
- ((...args) => new ProtocolHandler(...args, new Audience()));
1723
- const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) =>
1727
+ const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) =>
1724
1728
  this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
1725
1729
  );
1726
1730
 
@@ -1859,6 +1863,14 @@ export class Container
1859
1863
  this.connectionStateHandler.receivedConnectEvent(details);
1860
1864
  });
1861
1865
 
1866
+ deltaManager.on("establishingConnection", (reason: string) => {
1867
+ this.connectionStateHandler.establishingConnection(reason);
1868
+ });
1869
+
1870
+ deltaManager.on("cancelEstablishingConnection", (reason: string) => {
1871
+ this.connectionStateHandler.cancelEstablishingConnection(reason);
1872
+ });
1873
+
1862
1874
  deltaManager.on("disconnect", (reason: string, error?: IAnyDriverError) => {
1863
1875
  this.collabWindowTracker?.stopSequenceNumberUpdate();
1864
1876
  if (!this.closed) {
@@ -1935,8 +1947,8 @@ export class Container
1935
1947
  durationFromDisconnected =
1936
1948
  time - this.connectionTransitionTimes[ConnectionState.Disconnected];
1937
1949
  durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
1938
- } else {
1939
- // This info is of most interest on establishing connection only.
1950
+ } else if (value === ConnectionState.CatchingUp) {
1951
+ // This info is of most interesting while Catching Up.
1940
1952
  checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
1941
1953
  if (this.deltaManager.hasCheckpointSequenceNumber) {
1942
1954
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
@@ -229,12 +229,6 @@ class BlobOnlyStorage implements IDocumentStorageService {
229
229
  }
230
230
  }
231
231
 
232
- // runtime will write a tree to the summary containing only "attachment" type entries
233
- // which reference attachment blobs by ID. However, some drivers do not support this type
234
- // and will convert them to "blob" type entries. We want to avoid saving these to reduce
235
- // the size of stashed change blobs.
236
- const blobsTreeName = ".blobs";
237
-
238
232
  /**
239
233
  * Get blob contents of a snapshot tree from storage (or, ideally, cache)
240
234
  */
@@ -251,13 +245,10 @@ async function getBlobContentsFromTreeCore(
251
245
  tree: ISnapshotTree,
252
246
  blobs: ISerializableBlobContents,
253
247
  storage: IDocumentStorageService,
254
- root = true,
255
248
  ) {
256
249
  const treePs: Promise<any>[] = [];
257
- for (const [key, subTree] of Object.entries(tree.trees)) {
258
- if (!root || key !== blobsTreeName) {
259
- treePs.push(getBlobContentsFromTreeCore(subTree, blobs, storage, false));
260
- }
250
+ for (const subTree of Object.values(tree.trees)) {
251
+ treePs.push(getBlobContentsFromTreeCore(subTree, blobs, storage));
261
252
  }
262
253
  for (const id of Object.values(tree.blobs)) {
263
254
  const blob = await storage.readBlob(id);
@@ -281,12 +272,9 @@ export function getBlobContentsFromTreeWithBlobContents(
281
272
  function getBlobContentsFromTreeWithBlobContentsCore(
282
273
  tree: ISnapshotTreeWithBlobContents,
283
274
  blobs: ISerializableBlobContents,
284
- root = true,
285
275
  ) {
286
- for (const [key, subTree] of Object.entries(tree.trees)) {
287
- if (!root || key !== blobsTreeName) {
288
- getBlobContentsFromTreeWithBlobContentsCore(subTree, blobs, false);
289
- }
276
+ for (const subTree of Object.values(tree.trees)) {
277
+ getBlobContentsFromTreeWithBlobContentsCore(subTree, blobs);
290
278
  }
291
279
  for (const id of Object.values(tree.blobs)) {
292
280
  const blob = tree.blobsContents[id];
package/src/contracts.ts CHANGED
@@ -167,6 +167,16 @@ export interface IConnectionManagerFactoryArgs {
167
167
  * `undefined` indicates that user permissions are not yet known.
168
168
  */
169
169
  readonly readonlyChangeHandler: (readonly?: boolean) => void;
170
+
171
+ /**
172
+ * Called whenever we try to start establishing a new connection.
173
+ */
174
+ readonly establishConnectionHandler: (reason: string) => void;
175
+
176
+ /**
177
+ * Called whenever we cancel the connection in progress.
178
+ */
179
+ readonly cancelConnectionHandler: (reason: string) => void;
170
180
  }
171
181
 
172
182
  /**
@@ -24,6 +24,7 @@ import {
24
24
  normalizeError,
25
25
  logIfFalse,
26
26
  safeRaiseEvent,
27
+ isFluidError,
27
28
  ITelemetryLoggerExt,
28
29
  } from "@fluidframework/telemetry-utils";
29
30
  import {
@@ -45,6 +46,7 @@ import {
45
46
  DataCorruptionError,
46
47
  extractSafePropertiesFromMessage,
47
48
  DataProcessingError,
49
+ UsageError,
48
50
  } from "@fluidframework/container-utils";
49
51
  import { IConnectionManagerFactoryArgs, IConnectionManager } from "./contracts";
50
52
  import { DeltaQueue } from "./deltaQueue";
@@ -64,6 +66,8 @@ export interface IDeltaManagerInternalEvents extends IDeltaManagerEvents {
64
66
  (event: "throttled", listener: (error: IThrottlingWarning) => void);
65
67
  (event: "closed" | "disposed", listener: (error?: ICriticalContainerError) => void);
66
68
  (event: "connect", listener: (details: IConnectionDetailsInternal, opsBehind?: number) => void);
69
+ (event: "establishingConnection", listener: (reason: string) => void);
70
+ (event: "cancelEstablishingConnection", listener: (reason: string) => void);
67
71
  }
68
72
 
69
73
  /**
@@ -369,6 +373,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
369
373
  pongHandler: (latency: number) => this.emit("pong", latency),
370
374
  readonlyChangeHandler: (readonly?: boolean) =>
371
375
  safeRaiseEvent(this, this.logger, "readonly", readonly),
376
+ establishConnectionHandler: (reason: string) => this.establishingConnection(reason),
377
+ cancelConnectionHandler: (reason: string) => this.cancelEstablishingConnection(reason),
372
378
  };
373
379
 
374
380
  this.connectionManager = createConnectionManager(props);
@@ -408,6 +414,14 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
408
414
  // - inbound & inboundSignal are resumed in attachOpHandler() when we have handler setup
409
415
  }
410
416
 
417
+ private cancelEstablishingConnection(reason: string) {
418
+ this.emit("cancelEstablishingConnection", reason);
419
+ }
420
+
421
+ private establishingConnection(reason: string) {
422
+ this.emit("establishingConnection", reason);
423
+ }
424
+
411
425
  private connectHandler(connection: IConnectionDetailsInternal) {
412
426
  this.refreshDelayInfo(this.deltaStreamDelayId);
413
427
 
@@ -457,10 +471,6 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
457
471
  }
458
472
  }
459
473
 
460
- public dispose() {
461
- throw new Error("Not implemented.");
462
- }
463
-
464
474
  /**
465
475
  * Sets the sequence number from which inbound messages should be returned
466
476
  */
@@ -648,23 +658,51 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
648
658
  /**
649
659
  * Closes the connection and clears inbound & outbound queues.
650
660
  *
651
- * @param doDispose - should the DeltaManager treat this close call as a dispose?
652
- * Differences between close and dispose:
653
- * - dispose will emit "disposed" event while close emits "closed"
654
- * - dispose will remove all listeners
655
- * - dispose can be called after closure, but not vis versa
661
+ * Differences from dispose:
662
+ * - close will trigger readonly notification
663
+ * - close emits "closed"
664
+ * - close cannot be called after dispose
656
665
  */
657
- public close(error?: ICriticalContainerError, doDispose?: boolean): void {
666
+ public close(error?: ICriticalContainerError): void {
658
667
  if (this._closed) {
659
- if (doDispose === true) {
660
- this.disposeInternal(error);
661
- }
662
668
  return;
663
669
  }
664
670
  this._closed = true;
665
671
 
666
- this.connectionManager.dispose(error, doDispose !== true);
672
+ this.connectionManager.dispose(error, true /* switchToReadonly */);
673
+ this.clearQueues();
674
+ this.emit("closed", error);
675
+ }
676
+
677
+ /**
678
+ * Disposes the connection and clears the inbound & outbound queues.
679
+ *
680
+ * Differences from close:
681
+ * - dispose will emit "disposed"
682
+ * - dispose will remove all listeners
683
+ * - dispose can be called after closure
684
+ */
685
+ public dispose(error?: Error | ICriticalContainerError): void {
686
+ if (this._disposed) {
687
+ return;
688
+ }
689
+ if (error !== undefined && !isFluidError(error)) {
690
+ throw new UsageError("Error must be a Fluid error");
691
+ }
692
+
693
+ this._disposed = true;
694
+ this._closed = true; // We consider "disposed" as a further state than "closed"
667
695
 
696
+ this.connectionManager.dispose(error, false /* switchToReadonly */);
697
+ this.clearQueues();
698
+
699
+ // This needs to be the last thing we do (before removing listeners), as it causes
700
+ // Container to dispose context and break ability of data stores / runtime to "hear" from delta manager.
701
+ this.emit("disposed", error);
702
+ this.removeAllListeners();
703
+ }
704
+
705
+ private clearQueues() {
668
706
  this.closeAbortController.abort();
669
707
 
670
708
  this._inbound.clear();
@@ -677,25 +715,6 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
677
715
 
678
716
  // Drop pending messages - this will ensure catchUp() does not go into infinite loop
679
717
  this.pending = [];
680
-
681
- if (doDispose === true) {
682
- this.disposeInternal(error);
683
- } else {
684
- this.emit("closed", error);
685
- }
686
- }
687
-
688
- private disposeInternal(error?: ICriticalContainerError): void {
689
- if (this._disposed) {
690
- return;
691
- }
692
- this._disposed = true;
693
-
694
- // This needs to be the last thing we do (before removing listeners), as it causes
695
- // Container to dispose context and break ability of data stores / runtime to "hear"
696
- // from delta manager, including notification (above) about readonly state.
697
- this.emit("disposed", error);
698
- this.removeAllListeners();
699
718
  }
700
719
 
701
720
  public refreshDelayInfo(id: string) {
package/src/deltaQueue.ts CHANGED
@@ -20,8 +20,8 @@ export class DeltaQueue<T>
20
20
  private readonly q = new Deque<T>();
21
21
 
22
22
  /**
23
- * Tracks the number of pause requests for the queue
24
- * The DeltaQueue is create initially paused.
23
+ * Tracks the number of pause requests for the queue.
24
+ * The DeltaQueue is created initially paused.
25
25
  */
26
26
  private pauseCount = 1;
27
27
 
@@ -58,7 +58,6 @@ export class DeltaQueue<T>
58
58
 
59
59
  /**
60
60
  * @param worker - A callback to process a delta.
61
- * @param logger - For logging telemetry.
62
61
  */
63
62
  constructor(private readonly worker: (delta: T) => void) {
64
63
  super();
package/src/loader.ts CHANGED
@@ -79,8 +79,7 @@ export class RelativeLoader implements ILoader {
79
79
  return this.container;
80
80
  } else {
81
81
  ensureResolvedUrlDefined(this.container.resolvedUrl);
82
- const container = await Container.clone(
83
- this.container,
82
+ const container = await this.container.clone(
84
83
  {
85
84
  resolvedUrl: { ...this.container.resolvedUrl },
86
85
  version: request.headers?.[LoaderHeader.version] ?? undefined,
@@ -308,28 +307,41 @@ export class Loader implements IHostLoader {
308
307
  private readonly mc: MonitoringContext;
309
308
 
310
309
  constructor(loaderProps: ILoaderProps) {
311
- const scope: FluidObject<ILoader> = { ...loaderProps.scope };
312
- if (loaderProps.options?.provideScopeLoader !== false) {
313
- scope.ILoader = this;
314
- }
310
+ const {
311
+ urlResolver,
312
+ documentServiceFactory,
313
+ codeLoader,
314
+ options,
315
+ scope,
316
+ logger,
317
+ detachedBlobStorage,
318
+ configProvider,
319
+ protocolHandlerBuilder,
320
+ } = loaderProps;
321
+
315
322
  const telemetryProps = {
316
323
  loaderId: uuid(),
317
324
  loaderVersion: pkgVersion,
318
325
  };
319
326
 
320
327
  const subMc = mixinMonitoringContext(
321
- DebugLogger.mixinDebugLogger("fluid:telemetry", loaderProps.logger, {
328
+ DebugLogger.mixinDebugLogger("fluid:telemetry", logger, {
322
329
  all: telemetryProps,
323
330
  }),
324
331
  sessionStorageConfigProvider.value,
325
- loaderProps.configProvider,
332
+ configProvider,
326
333
  );
327
334
 
328
335
  this.services = {
329
- ...loaderProps,
330
- scope,
336
+ urlResolver,
337
+ documentServiceFactory,
338
+ codeLoader,
339
+ options: options ?? {},
340
+ scope:
341
+ options?.provideScopeLoader !== false ? { ...scope, ILoader: this } : { ...scope },
342
+ detachedBlobStorage,
343
+ protocolHandlerBuilder,
331
344
  subLogger: subMc.logger,
332
- options: loaderProps.options ?? {},
333
345
  };
334
346
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.services.subLogger, "Loader"));
335
347
  }
@@ -431,11 +443,18 @@ export class Loader implements IHostLoader {
431
443
  }
432
444
  }
433
445
 
434
- const { canCache, fromSequenceNumber } = this.parseHeader(parsed, request);
435
- const shouldCache = pendingLocalState !== undefined ? false : canCache;
446
+ request.headers ??= {};
447
+ // If set in both query string and headers, use query string. Also write the value from the query string into the header either way.
448
+ request.headers[LoaderHeader.version] =
449
+ parsed.version ?? request.headers[LoaderHeader.version];
450
+ const canCache =
451
+ this.cachingEnabled &&
452
+ request.headers[LoaderHeader.cache] !== false &&
453
+ pendingLocalState === undefined;
454
+ const fromSequenceNumber = request.headers[LoaderHeader.sequenceNumber] ?? -1;
436
455
 
437
456
  let container: Container;
438
- if (shouldCache) {
457
+ if (canCache) {
439
458
  const key = this.getKeyForContainerCache(request, parsed);
440
459
  const maybeContainer = await this.containers.get(key);
441
460
  if (maybeContainer !== undefined) {
@@ -469,32 +488,6 @@ export class Loader implements IHostLoader {
469
488
  return this.services.options.cache !== false;
470
489
  }
471
490
 
472
- private canCacheForRequest(headers: IRequestHeader): boolean {
473
- return this.cachingEnabled && headers[LoaderHeader.cache] !== false;
474
- }
475
-
476
- private parseHeader(parsed: IParsedUrl, request: IRequest) {
477
- let fromSequenceNumber = -1;
478
-
479
- request.headers = request.headers ?? {};
480
-
481
- const headerSeqNum = request.headers[LoaderHeader.sequenceNumber];
482
- if (headerSeqNum !== undefined) {
483
- fromSequenceNumber = headerSeqNum;
484
- }
485
-
486
- // If set in both query string and headers, use query string
487
- request.headers[LoaderHeader.version] =
488
- parsed.version ?? request.headers[LoaderHeader.version];
489
-
490
- const canCache = this.canCacheForRequest(request.headers);
491
-
492
- return {
493
- canCache,
494
- fromSequenceNumber,
495
- };
496
- }
497
-
498
491
  private async loadContainer(
499
492
  request: IRequest,
500
493
  resolvedUrl: IResolvedUrl,
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-internal.5.0.1";
9
+ export const pkgVersion = "2.0.0-internal.5.1.0";