@fluidframework/container-loader 0.52.0-44610 → 0.53.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 (55) hide show
  1. package/dist/connectionStateHandler.d.ts +1 -0
  2. package/dist/connectionStateHandler.d.ts.map +1 -1
  3. package/dist/connectionStateHandler.js +6 -0
  4. package/dist/connectionStateHandler.js.map +1 -1
  5. package/dist/container.d.ts +0 -21
  6. package/dist/container.d.ts.map +1 -1
  7. package/dist/container.js +96 -123
  8. package/dist/container.js.map +1 -1
  9. package/dist/containerContext.d.ts +1 -0
  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 +31 -33
  16. package/dist/deltaManager.js.map +1 -1
  17. package/dist/loader.d.ts +5 -0
  18. package/dist/loader.d.ts.map +1 -1
  19. package/dist/loader.js +6 -1
  20. package/dist/loader.js.map +1 -1
  21. package/dist/packageVersion.d.ts +1 -1
  22. package/dist/packageVersion.d.ts.map +1 -1
  23. package/dist/packageVersion.js +1 -1
  24. package/dist/packageVersion.js.map +1 -1
  25. package/lib/connectionStateHandler.d.ts +1 -0
  26. package/lib/connectionStateHandler.d.ts.map +1 -1
  27. package/lib/connectionStateHandler.js +6 -0
  28. package/lib/connectionStateHandler.js.map +1 -1
  29. package/lib/container.d.ts +0 -21
  30. package/lib/container.d.ts.map +1 -1
  31. package/lib/container.js +97 -124
  32. package/lib/container.js.map +1 -1
  33. package/lib/containerContext.d.ts +1 -0
  34. package/lib/containerContext.d.ts.map +1 -1
  35. package/lib/containerContext.js +4 -0
  36. package/lib/containerContext.js.map +1 -1
  37. package/lib/deltaManager.d.ts +0 -7
  38. package/lib/deltaManager.d.ts.map +1 -1
  39. package/lib/deltaManager.js +31 -33
  40. package/lib/deltaManager.js.map +1 -1
  41. package/lib/loader.d.ts +5 -0
  42. package/lib/loader.d.ts.map +1 -1
  43. package/lib/loader.js +6 -1
  44. package/lib/loader.js.map +1 -1
  45. package/lib/packageVersion.d.ts +1 -1
  46. package/lib/packageVersion.d.ts.map +1 -1
  47. package/lib/packageVersion.js +1 -1
  48. package/lib/packageVersion.js.map +1 -1
  49. package/package.json +11 -11
  50. package/src/connectionStateHandler.ts +8 -0
  51. package/src/container.ts +126 -148
  52. package/src/containerContext.ts +4 -0
  53. package/src/deltaManager.ts +35 -35
  54. package/src/loader.ts +29 -19
  55. 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,
@@ -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
  }
@@ -576,7 +562,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
576
562
  {
577
563
  all: {
578
564
  clientType, // Differentiating summarizer container from main container
579
- loaderVersion: pkgVersion,
580
565
  containerId: uuid(),
581
566
  docId: () => this.id,
582
567
  containerAttachState: () => this._attachState,
@@ -672,22 +657,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
672
657
  switch (event) {
673
658
  case dirtyContainerEvent:
674
659
  if (this._dirtyContainer) {
675
- listener(dirtyContainerEvent);
660
+ listener();
676
661
  }
677
662
  break;
678
663
  case savedContainerEvent:
679
664
  if (!this._dirtyContainer) {
680
- listener(savedContainerEvent);
665
+ listener();
681
666
  }
682
667
  break;
683
668
  case connectedEventName:
684
669
  if (this.connected) {
685
- listener(event, this.clientId);
670
+ listener(this.clientId);
686
671
  }
687
672
  break;
688
673
  case disconnectedEventName:
689
674
  if (!this.connected) {
690
- listener(event);
675
+ listener();
691
676
  }
692
677
  break;
693
678
  default:
@@ -719,6 +704,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
719
704
 
720
705
  this._protocolHandler?.close();
721
706
 
707
+ this.connectionStateHandler.dispose();
708
+
722
709
  this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
723
710
 
724
711
  assert(this.connectionState === ConnectionState.Disconnected,
@@ -786,113 +773,113 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
786
773
  }
787
774
 
788
775
  public async attach(request: IRequest): Promise<void> {
789
- if (this._lifecycleState !== "loaded") {
790
- throw new UsageError(`containerNotValidForAttach [${this._lifecycleState}]`);
791
- }
776
+ await PerformanceEvent.timedExecAsync(this.logger, { eventName: "Attach" }, async () => {
777
+ if (this._lifecycleState !== "loaded") {
778
+ throw new UsageError(`containerNotValidForAttach [${this._lifecycleState}]`);
779
+ }
792
780
 
793
- // If container is already attached or attach is in progress, throw an error.
794
- assert(this._attachState === AttachState.Detached && !this.attachStarted,
795
- 0x205 /* "attach() called more than once" */);
796
- 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;
797
785
 
798
- // If attachment blobs were uploaded in detached state we will go through a different attach flow
799
- const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
800
- && 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;
801
789
 
802
- try {
803
- assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
804
-
805
- let summary: ISummaryTree;
806
- if (!hasAttachmentBlobs) {
807
- // Get the document state post attach - possibly can just call attach but we need to change the
808
- // semantics around what the attach means as far as async code goes.
809
- const appSummary: ISummaryTree = this.context.createSummary();
810
- const protocolSummary = this.captureProtocolSummary();
811
- summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
812
-
813
- // Set the state as attaching as we are starting the process of attaching container.
814
- // This should be fired after taking the summary because it is the place where we are
815
- // starting to attach the container to storage.
816
- // Also, this should only be fired in detached container.
817
- this._attachState = AttachState.Attaching;
818
- this.context.notifyAttaching();
819
- }
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
+ }
820
809
 
821
- // Actually go and create the resolved document
822
- const createNewResolvedUrl = await this.urlResolver.resolve(request);
823
- ensureFluidResolvedUrl(createNewResolvedUrl);
824
- if (this.service === undefined) {
825
- this.service = await runWithRetry(
826
- async () => this.serviceFactory.createContainer(
827
- summary,
828
- createNewResolvedUrl,
829
- this.subLogger,
830
- ),
831
- "containerAttach",
832
- this.logger,
833
- {}, // progress
834
- );
835
- }
836
- const resolvedUrl = this.service.resolvedUrl;
837
- ensureFluidResolvedUrl(resolvedUrl);
838
- this._resolvedUrl = resolvedUrl;
839
- await this.connectStorageService();
840
-
841
- if (hasAttachmentBlobs) {
842
- // upload blobs to storage
843
- assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
844
-
845
- // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
846
- // support blob handles that only know about the local IDs
847
- const redirectTable = new Map<string, string>();
848
- // if new blobs are added while uploading, upload them too
849
- while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
850
- const newIds = this.loader.services.detachedBlobStorage.getBlobIds().filter(
851
- (id) => !redirectTable.has(id));
852
- for (const id of newIds) {
853
- const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
854
- const response = await this.storageService.createBlob(blob);
855
- redirectTable.set(id, response.id);
856
- }
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
+ );
857
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
+ }
858
847
 
859
- // take summary and upload
860
- const appSummary: ISummaryTree = this.context.createSummary(redirectTable);
861
- const protocolSummary = this.captureProtocolSummary();
862
- 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);
863
852
 
864
- this._attachState = AttachState.Attaching;
865
- this.context.notifyAttaching();
853
+ this._attachState = AttachState.Attaching;
854
+ this.context.notifyAttaching();
866
855
 
867
- await this.storageService.uploadSummaryWithContext(summary, {
868
- referenceSequenceNumber: 0,
869
- ackHandle: undefined,
870
- proposalHandle: undefined,
871
- });
872
- }
856
+ await this.storageService.uploadSummaryWithContext(summary, {
857
+ referenceSequenceNumber: 0,
858
+ ackHandle: undefined,
859
+ proposalHandle: undefined,
860
+ });
861
+ }
873
862
 
874
- this._attachState = AttachState.Attached;
875
- this.emit("attached");
863
+ this._attachState = AttachState.Attached;
864
+ this.emit("attached");
876
865
 
877
- // Propagate current connection state through the system.
878
- this.propagateConnectionState();
879
- if (!this.closed) {
880
- this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
881
- }
882
- } catch(error) {
883
- // we should retry upon any retriable errors, so we shouldn't see them here
884
- assert(!canRetryOnError(error), 0x24f /* "retriable error thrown from attach()" */);
885
-
886
- // add resolved URL on error object so that host has the ability to find this document and delete it
887
- const newError = normalizeError(error);
888
- const resolvedUrl = this.resolvedUrl;
889
- if (resolvedUrl) {
890
- ensureFluidResolvedUrl(resolvedUrl);
891
- 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;
892
880
  }
893
- this.close(newError);
894
- throw newError;
895
- }
881
+ },
882
+ { start: true, end: true, cancel: "generic" });
896
883
  }
897
884
 
898
885
  public async request(path: IRequest): Promise<IResponse> {
@@ -1539,7 +1526,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1539
1526
  if (this.clientDetailsOverride !== undefined) {
1540
1527
  merge(client.details, this.clientDetailsOverride);
1541
1528
  }
1542
-
1529
+ client.details.environment = [client.details.environment, ` loaderVersion:${pkgVersion}`].join(";");
1543
1530
  return client;
1544
1531
  }
1545
1532
 
@@ -1550,17 +1537,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1550
1537
  * If it's not true, runtime is not in position to send ops.
1551
1538
  */
1552
1539
  private activeConnection() {
1553
- const active = this.connectionState === ConnectionState.Connected &&
1540
+ return this.connectionState === ConnectionState.Connected &&
1554
1541
  this._deltaManager.connectionMode === "write";
1555
-
1556
- // Check for presence of current client in quorum for "write" connections - inactive clients
1557
- // would get leave op after some long timeout (5 min) and that should automatically transition
1558
- // state to "read" mode.
1559
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1560
- assert(!active || this.getQuorum().getMember(this.clientId!) !== undefined,
1561
- 0x276 /* "active connection not present in quorum" */);
1562
-
1563
- return active;
1564
1542
  }
1565
1543
 
1566
1544
  private createDeltaManager() {
@@ -191,6 +191,10 @@ export class ContainerContext implements IContainerContext {
191
191
  this.attachListener();
192
192
  }
193
193
 
194
+ public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
195
+ return (this.quorum.get("code") ?? this.quorum.get("code2")) as IFluidCodeDetails | undefined;
196
+ }
197
+
194
198
  public dispose(error?: Error): void {
195
199
  if (this._disposed) {
196
200
  return;
@@ -93,6 +93,8 @@ const createReconnectError = (fluidErrorCode: string, err: any) =>
93
93
  (errorMessage: string) => new GenericNetworkError(fluidErrorCode, errorMessage, true /* canRetry */),
94
94
  );
95
95
 
96
+ const fatalConnectErrorProp = { fatalConnectError: true };
97
+
96
98
  export interface IConnectionArgs {
97
99
  mode?: ConnectionMode;
98
100
  fetchOpsFromStorage?: boolean;
@@ -331,7 +333,7 @@ export class DeltaManager
331
333
  public get connectionMode(): ConnectionMode {
332
334
  assert(!this.downgradedConnection || this.connection?.mode === "write",
333
335
  0x277 /* "Did we forget to reset downgradedConnection on new connection?" */);
334
- if (this.connection === undefined || this.downgradedConnection) {
336
+ if (this.connection === undefined) {
335
337
  return "read";
336
338
  }
337
339
  return this.connection.mode;
@@ -347,23 +349,13 @@ export class DeltaManager
347
349
  * and do not know if user has write access to a file.
348
350
  * @deprecated - use readOnlyInfo
349
351
  */
350
- public get readonly() {
352
+ public get readonly() {
351
353
  if (this._forceReadonly) {
352
354
  return true;
353
355
  }
354
356
  return this._readonlyPermissions;
355
357
  }
356
358
 
357
- /**
358
- * Tells if user has no write permissions for file in storage
359
- * It is undefined if we have not yet established websocket connection
360
- * and do not know if user has write access to a file.
361
- * @deprecated - use readOnlyInfo
362
- */
363
- public get readonlyPermissions() {
364
- return this._readonlyPermissions;
365
- }
366
-
367
359
  public get readOnlyInfo(): ReadOnlyInfo {
368
360
  const storageOnly = this.connection !== undefined && this.connection instanceof NoDeltaStream;
369
361
  if (storageOnly || this._forceReadonly || this._readonlyPermissions === true) {
@@ -402,6 +394,7 @@ export class DeltaManager
402
394
  return {
403
395
  ...common,
404
396
  connectionMode: this.connectionMode,
397
+ relayServiceAgent: this.connection.relayServiceAgent,
405
398
  };
406
399
  } else {
407
400
  return {
@@ -452,15 +445,15 @@ export class DeltaManager
452
445
  value: readonly,
453
446
  });
454
447
  }
455
- const oldValue = this.readonly;
448
+ const oldValue = this.readOnlyInfo.readonly;
456
449
  this._forceReadonly = readonly;
457
450
 
458
- if (oldValue !== this.readonly) {
451
+ if (oldValue !== this.readOnlyInfo.readonly) {
459
452
  assert(this._reconnectMode !== ReconnectMode.Never,
460
453
  0x279 /* "API is not supported for non-connecting or closed container" */);
461
454
 
462
455
  let reconnect = false;
463
- if (this.readonly === true) {
456
+ if (this.readOnlyInfo.readonly === true) {
464
457
  // If we switch to readonly while connected, we should disconnect first
465
458
  // See comment in the "readonly" event handler to deltaManager set up by
466
459
  // the ContainerRuntime constructor
@@ -473,7 +466,7 @@ export class DeltaManager
473
466
 
474
467
  reconnect = this.disconnectFromDeltaStream("Force readonly");
475
468
  }
476
- safeRaiseEvent(this, this.logger, "readonly", this.readonly);
469
+ safeRaiseEvent(this, this.logger, "readonly", this.readOnlyInfo.readonly);
477
470
  if (reconnect) {
478
471
  // reconnect if we disconnected from before.
479
472
  this.triggerConnect({ reason: "forceReadonly", mode: "read", fetchOpsFromStorage: false });
@@ -511,10 +504,10 @@ export class DeltaManager
511
504
  }
512
505
 
513
506
  private set_readonlyPermissions(readonly: boolean) {
514
- const oldValue = this.readonly;
507
+ const oldValue = this.readOnlyInfo.readonly;
515
508
  this._readonlyPermissions = readonly;
516
- if (oldValue !== this.readonly) {
517
- safeRaiseEvent(this, this.logger, "readonly", this.readonly);
509
+ if (oldValue !== this.readOnlyInfo.readonly) {
510
+ safeRaiseEvent(this, this.logger, "readonly", this.readOnlyInfo.readonly);
518
511
  }
519
512
  }
520
513
 
@@ -640,7 +633,6 @@ export class DeltaManager
640
633
  existing: connection.existing,
641
634
  checkpointSequenceNumber: connection.checkpointSequenceNumber,
642
635
  get initialClients() { return connection.initialClients; },
643
- maxMessageSize: connection.serviceConfiguration.maxMessageSize,
644
636
  mode: connection.mode,
645
637
  serviceConfiguration: connection.serviceConfiguration,
646
638
  version: connection.version,
@@ -713,9 +705,7 @@ export class DeltaManager
713
705
  }
714
706
 
715
707
  const docService = this.serviceProvider();
716
- if (docService === undefined) {
717
- throw new Error("Container is not attached");
718
- }
708
+ assert(docService !== undefined, 0x2a7 /* "Container is not attached" */);
719
709
 
720
710
  if (docService.policies?.storageOnly === true) {
721
711
  const connection = new NoDeltaStream();
@@ -758,7 +748,7 @@ export class DeltaManager
758
748
 
759
749
  // Socket.io error when we connect to wrong socket, or hit some multiplexing bug
760
750
  if (!canRetryOnError(origError)) {
761
- const error = normalizeError(origError);
751
+ const error = normalizeError(origError, { props: fatalConnectErrorProp });
762
752
  this.close(error);
763
753
  throw error;
764
754
  }
@@ -817,9 +807,11 @@ export class DeltaManager
817
807
  const cleanupAndReject = (error) => {
818
808
  this.connectionP = undefined;
819
809
  this.removeListener("closed", cleanupAndReject);
810
+
820
811
  // This error came from some logic error in this file. Fail-fast to learn and fix the issue faster
821
- this.close(error);
822
- deferred.reject(error);
812
+ const normalizedError = normalizeError(error, { props: fatalConnectErrorProp });
813
+ this.close(normalizedError);
814
+ deferred.reject(normalizedError);
823
815
  };
824
816
  this.on("closed", cleanupAndReject);
825
817
 
@@ -857,7 +849,7 @@ export class DeltaManager
857
849
  // const serializedContent = JSON.stringify(this.messageBuffer);
858
850
  // const maxOpSize = this.context.deltaManager.maxMessageSize;
859
851
 
860
- if (this.readonly === true) {
852
+ if (this.readOnlyInfo.readonly === true) {
861
853
  assert(this.readOnlyInfo.readonly === true, 0x1f0 /* "Unexpected mismatch in readonly" */);
862
854
  const error = new GenericError("deltaManagerReadonlySubmit", undefined /* error */, {
863
855
  readonly: this.readOnlyInfo.readonly,
@@ -885,7 +877,7 @@ export class DeltaManager
885
877
  // Note that we also want nacks to be rare and be treated as catastrophic failures.
886
878
  // Be careful with reentrancy though - disconnected event should not be be raised in the
887
879
  // middle of the current workflow, but rather on clean stack!
888
- if (this.connectionMode === "read") {
880
+ if (this.connectionMode === "read" || this.downgradedConnection) {
889
881
  if (!this.pendingReconnect) {
890
882
  this.pendingReconnect = true;
891
883
  Promise.resolve().then(async () => {
@@ -1024,7 +1016,8 @@ export class DeltaManager
1024
1016
  from, // inclusive
1025
1017
  to, // exclusive
1026
1018
  controller.signal,
1027
- cacheOnly);
1019
+ cacheOnly,
1020
+ this.fetchReason);
1028
1021
 
1029
1022
  // eslint-disable-next-line no-constant-condition
1030
1023
  while (true) {
@@ -1059,8 +1052,12 @@ export class DeltaManager
1059
1052
 
1060
1053
  this.closeAbortController.abort();
1061
1054
 
1055
+ const disconnectReason = error !== undefined
1056
+ ? `Closing DeltaManager (${error.message})`
1057
+ : "Closing DeltaManager";
1058
+
1062
1059
  // This raises "disconnect" event if we have active connection.
1063
- this.disconnectFromDeltaStream(error !== undefined ? `${error.message}` : "Container closed");
1060
+ this.disconnectFromDeltaStream(disconnectReason);
1064
1061
 
1065
1062
  this._inbound.clear();
1066
1063
  this._outbound.clear();
@@ -1201,7 +1198,7 @@ export class DeltaManager
1201
1198
 
1202
1199
  if (this.closed) {
1203
1200
  // Raise proper events, Log telemetry event and close connection.
1204
- this.disconnectFromDeltaStream(`Disconnect on close`);
1201
+ this.disconnectFromDeltaStream("DeltaManager already closed");
1205
1202
  return;
1206
1203
  }
1207
1204
 
@@ -1233,6 +1230,9 @@ export class DeltaManager
1233
1230
  clientId: connection.clientId,
1234
1231
  mode: connection.mode,
1235
1232
  };
1233
+ if (connection.relayServiceAgent !== undefined) {
1234
+ this.connectionStateProps.relayServiceAgent = connection.relayServiceAgent;
1235
+ }
1236
1236
  this._hasCheckpointSequenceNumber = false;
1237
1237
 
1238
1238
  // Some storages may provide checkpointSequenceNumber to identify how far client is behind.
@@ -1368,11 +1368,13 @@ export class DeltaManager
1368
1368
  const canRetry = error !== undefined ? canRetryOnError(error) : true;
1369
1369
 
1370
1370
  // If reconnection is not an option, close the DeltaManager
1371
- if (this.reconnectMode === ReconnectMode.Never || !canRetry) {
1371
+ if (!canRetry) {
1372
+ this.close(normalizeError(error, { props: fatalConnectErrorProp }));
1373
+ } else if (this.reconnectMode === ReconnectMode.Never) {
1372
1374
  // Do not raise container error if we are closing just because we lost connection.
1373
1375
  // Those errors (like IdleDisconnect) would show up in telemetry dashboards and
1374
1376
  // are very misleading, as first initial reaction - some logic is broken.
1375
- this.close(canRetry ? undefined : error);
1377
+ this.close();
1376
1378
  }
1377
1379
 
1378
1380
  // If closed then we can't reconnect
@@ -1580,8 +1582,6 @@ export class DeltaManager
1580
1582
  // We have been kicked out from quorum
1581
1583
  this.logger.sendPerformanceEvent({ eventName: "ReadConnectionTransition" });
1582
1584
  this.downgradedConnection = true;
1583
- assert(this.connectionMode === "read",
1584
- 0x27c /* "effective connectionMode should be 'read' after downgrade" */);
1585
1585
  }
1586
1586
  }
1587
1587