@fluidframework/container-loader 2.0.0-dev.6.4.0.192049 → 2.0.0-dev.7.2.0.204906

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 (146) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/api-extractor.json +9 -1
  3. package/api-report/container-loader.api.md +153 -0
  4. package/dist/catchUpMonitor.d.ts +2 -2
  5. package/dist/catchUpMonitor.d.ts.map +1 -1
  6. package/dist/connectionManager.d.ts +2 -15
  7. package/dist/connectionManager.d.ts.map +1 -1
  8. package/dist/connectionManager.js +117 -100
  9. package/dist/connectionManager.js.map +1 -1
  10. package/dist/connectionState.js +1 -1
  11. package/dist/connectionState.js.map +1 -1
  12. package/dist/connectionStateHandler.js +9 -9
  13. package/dist/connectionStateHandler.js.map +1 -1
  14. package/dist/container-loader-alpha.d.ts +333 -0
  15. package/dist/container-loader-beta.d.ts +333 -0
  16. package/dist/container-loader-public.d.ts +333 -0
  17. package/dist/container-loader-untrimmed.d.ts +333 -0
  18. package/dist/container.d.ts +22 -2
  19. package/dist/container.d.ts.map +1 -1
  20. package/dist/container.js +235 -222
  21. package/dist/container.js.map +1 -1
  22. package/dist/containerContext.js +16 -16
  23. package/dist/containerContext.js.map +1 -1
  24. package/dist/containerStorageAdapter.d.ts +1 -1
  25. package/dist/containerStorageAdapter.d.ts.map +1 -1
  26. package/dist/containerStorageAdapter.js +9 -11
  27. package/dist/containerStorageAdapter.js.map +1 -1
  28. package/dist/contracts.d.ts +5 -4
  29. package/dist/contracts.d.ts.map +1 -1
  30. package/dist/contracts.js +1 -1
  31. package/dist/contracts.js.map +1 -1
  32. package/dist/debugLogger.d.ts.map +1 -1
  33. package/dist/debugLogger.js +5 -4
  34. package/dist/debugLogger.js.map +1 -1
  35. package/dist/deltaManager.d.ts.map +1 -1
  36. package/dist/deltaManager.js +92 -96
  37. package/dist/deltaManager.js.map +1 -1
  38. package/dist/deltaQueue.js +14 -14
  39. package/dist/deltaQueue.js.map +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +6 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/loader.d.ts +6 -9
  45. package/dist/loader.d.ts.map +1 -1
  46. package/dist/loader.js +26 -91
  47. package/dist/loader.js.map +1 -1
  48. package/dist/location-redirection-utilities/index.d.ts +6 -0
  49. package/dist/location-redirection-utilities/index.d.ts.map +1 -0
  50. package/dist/location-redirection-utilities/index.js +11 -0
  51. package/dist/location-redirection-utilities/index.js.map +1 -0
  52. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
  53. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  54. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +51 -0
  55. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  56. package/dist/packageVersion.d.ts +1 -1
  57. package/dist/packageVersion.js +1 -1
  58. package/dist/packageVersion.js.map +1 -1
  59. package/dist/protocol.d.ts +1 -2
  60. package/dist/protocol.d.ts.map +1 -1
  61. package/dist/protocol.js +3 -5
  62. package/dist/protocol.js.map +1 -1
  63. package/dist/retriableDocumentStorageService.d.ts +3 -2
  64. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  65. package/dist/retriableDocumentStorageService.js +18 -11
  66. package/dist/retriableDocumentStorageService.js.map +1 -1
  67. package/dist/tsdoc-metadata.json +1 -1
  68. package/dist/utils.d.ts +23 -1
  69. package/dist/utils.d.ts.map +1 -1
  70. package/dist/utils.js +11 -3
  71. package/dist/utils.js.map +1 -1
  72. package/lib/catchUpMonitor.d.ts +2 -2
  73. package/lib/catchUpMonitor.d.ts.map +1 -1
  74. package/lib/connectionManager.d.ts +2 -15
  75. package/lib/connectionManager.d.ts.map +1 -1
  76. package/lib/connectionManager.js +120 -101
  77. package/lib/connectionManager.js.map +1 -1
  78. package/lib/connectionStateHandler.js +9 -9
  79. package/lib/connectionStateHandler.js.map +1 -1
  80. package/lib/container.d.ts +22 -2
  81. package/lib/container.d.ts.map +1 -1
  82. package/lib/container.js +236 -223
  83. package/lib/container.js.map +1 -1
  84. package/lib/containerContext.js +16 -16
  85. package/lib/containerContext.js.map +1 -1
  86. package/lib/containerStorageAdapter.d.ts +1 -1
  87. package/lib/containerStorageAdapter.d.ts.map +1 -1
  88. package/lib/containerStorageAdapter.js +9 -11
  89. package/lib/containerStorageAdapter.js.map +1 -1
  90. package/lib/contracts.d.ts +5 -4
  91. package/lib/contracts.d.ts.map +1 -1
  92. package/lib/contracts.js.map +1 -1
  93. package/lib/debugLogger.d.ts.map +1 -1
  94. package/lib/debugLogger.js +5 -4
  95. package/lib/debugLogger.js.map +1 -1
  96. package/lib/deltaManager.d.ts.map +1 -1
  97. package/lib/deltaManager.js +92 -96
  98. package/lib/deltaManager.js.map +1 -1
  99. package/lib/deltaQueue.js +14 -14
  100. package/lib/deltaQueue.js.map +1 -1
  101. package/lib/index.d.ts +2 -0
  102. package/lib/index.d.ts.map +1 -1
  103. package/lib/index.js +2 -0
  104. package/lib/index.js.map +1 -1
  105. package/lib/loader.d.ts +6 -9
  106. package/lib/loader.d.ts.map +1 -1
  107. package/lib/loader.js +27 -92
  108. package/lib/loader.js.map +1 -1
  109. package/lib/location-redirection-utilities/index.d.ts +6 -0
  110. package/lib/location-redirection-utilities/index.d.ts.map +1 -0
  111. package/lib/location-redirection-utilities/index.js +6 -0
  112. package/lib/location-redirection-utilities/index.js.map +1 -0
  113. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
  114. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  115. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +46 -0
  116. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  117. package/lib/packageVersion.d.ts +1 -1
  118. package/lib/packageVersion.js +1 -1
  119. package/lib/packageVersion.js.map +1 -1
  120. package/lib/protocol.d.ts +1 -2
  121. package/lib/protocol.d.ts.map +1 -1
  122. package/lib/protocol.js +1 -3
  123. package/lib/protocol.js.map +1 -1
  124. package/lib/retriableDocumentStorageService.d.ts +3 -2
  125. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  126. package/lib/retriableDocumentStorageService.js +18 -11
  127. package/lib/retriableDocumentStorageService.js.map +1 -1
  128. package/lib/utils.d.ts +23 -1
  129. package/lib/utils.d.ts.map +1 -1
  130. package/lib/utils.js +9 -1
  131. package/lib/utils.js.map +1 -1
  132. package/package.json +24 -26
  133. package/src/connectionManager.ts +69 -34
  134. package/src/container.ts +48 -30
  135. package/src/containerStorageAdapter.ts +3 -9
  136. package/src/contracts.ts +8 -4
  137. package/src/debugLogger.ts +5 -1
  138. package/src/deltaManager.ts +16 -31
  139. package/src/index.ts +5 -0
  140. package/src/loader.ts +31 -99
  141. package/src/location-redirection-utilities/index.ts +9 -0
  142. package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +59 -0
  143. package/src/packageVersion.ts +1 -1
  144. package/src/protocol.ts +2 -6
  145. package/src/retriableDocumentStorageService.ts +29 -15
  146. package/src/utils.ts +23 -1
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, ITelemetryProperties } from "@fluidframework/core-interfaces";
6
+ import { IDisposable, ITelemetryProperties, LogLevel } from "@fluidframework/core-interfaces";
7
7
  import { assert } from "@fluidframework/core-utils";
8
8
  import { performance, TypedEventEmitter } from "@fluid-internal/client-utils";
9
9
  import {
@@ -16,6 +16,8 @@ import {
16
16
  IDocumentService,
17
17
  IDocumentDeltaConnection,
18
18
  IDocumentDeltaConnectionEvents,
19
+ // eslint-disable-next-line import/no-deprecated
20
+ DriverErrorType,
19
21
  } from "@fluidframework/driver-definitions";
20
22
  import {
21
23
  canRetryOnError,
@@ -45,6 +47,7 @@ import {
45
47
  import {
46
48
  formatTick,
47
49
  GenericError,
50
+ isFluidError,
48
51
  ITelemetryLoggerExt,
49
52
  normalizeError,
50
53
  UsageError,
@@ -112,7 +115,15 @@ class NoDeltaStream
112
115
  blockSize: 0,
113
116
  };
114
117
  checkpointSequenceNumber?: number | undefined = undefined;
115
- constructor(public readonly storageOnlyReason?: string) {
118
+ /**
119
+ * Connection which is not connected to socket.
120
+ * @param storageOnlyReason - Reason on why the connection to delta stream is not allowed.
121
+ * @param readonlyConnectionReason - reason/error if any which lead to using NoDeltaStream.
122
+ */
123
+ constructor(
124
+ public readonly storageOnlyReason?: string,
125
+ public readonly readonlyConnectionReason?: IConnectionStateChangeReason,
126
+ ) {
116
127
  super();
117
128
  }
118
129
  submit(messages: IDocumentMessage[]): void {
@@ -409,7 +420,7 @@ export class ConnectionManager implements IConnectionManager {
409
420
  // Notify everyone we are in read-only state.
410
421
  // Useful for data stores in case we hit some critical error,
411
422
  // to switch to a mode where user edits are not accepted
412
- this.set_readonlyPermissions(true, oldReadonlyValue);
423
+ this.set_readonlyPermissions(true, oldReadonlyValue, disconnectReason);
413
424
  }
414
425
  }
415
426
 
@@ -432,21 +443,7 @@ export class ConnectionManager implements IConnectionManager {
432
443
  }
433
444
 
434
445
  /**
435
- * Sends signal to runtime (and data stores) to be read-only.
436
- * Hosts may have read only views, indicating to data stores that no edits are allowed.
437
- * This is independent from this._readonlyPermissions (permissions) and this.connectionMode
438
- * (server can return "write" mode even when asked for "read")
439
- * Leveraging same "readonly" event as runtime & data stores should behave the same in such case
440
- * as in read-only permissions.
441
- * But this.active can be used by some DDSes to figure out if ops can be sent
442
- * (for example, read-only view still participates in code proposals / upgrades decisions)
443
- *
444
- * Forcing Readonly does not prevent DDS from generating ops. It is up to user code to honour
445
- * the readonly flag. If ops are generated, they will accumulate locally and not be sent. If
446
- * there are pending in the outbound queue, it will stop sending until force readonly is
447
- * cleared.
448
- *
449
- * @param readonly - set or clear force readonly.
446
+ * {@inheritDoc Container.forceReadonly}
450
447
  */
451
448
  public forceReadonly(readonly: boolean) {
452
449
  if (readonly !== this._forceReadonly) {
@@ -487,10 +484,11 @@ export class ConnectionManager implements IConnectionManager {
487
484
  private set_readonlyPermissions(
488
485
  newReadonlyValue: boolean,
489
486
  oldReadonlyValue: boolean | undefined,
487
+ readonlyConnectionReason?: IConnectionStateChangeReason,
490
488
  ) {
491
489
  this._readonlyPermissions = newReadonlyValue;
492
490
  if (oldReadonlyValue !== this.readonly) {
493
- this.props.readonlyChangeHandler(this.readonly);
491
+ this.props.readonlyChangeHandler(this.readonly, readonlyConnectionReason);
494
492
  }
495
493
  }
496
494
 
@@ -587,9 +585,33 @@ export class ConnectionManager implements IConnectionManager {
587
585
  this.logger.sendTelemetryEvent({ eventName: "ReceivedClosedConnection" });
588
586
  connection = undefined;
589
587
  }
588
+ this.logger.sendTelemetryEvent(
589
+ {
590
+ eventName: "ConnectionReceived",
591
+ connected: connection !== undefined && connection.disposed === false,
592
+ },
593
+ undefined,
594
+ LogLevel.verbose,
595
+ );
590
596
  } catch (origError: any) {
591
597
  if (isDeltaStreamConnectionForbiddenError(origError)) {
592
- connection = new NoDeltaStream(origError.storageOnlyReason);
598
+ connection = new NoDeltaStream(origError.storageOnlyReason, {
599
+ text: origError.message,
600
+ error: origError,
601
+ });
602
+ requestedMode = "read";
603
+ break;
604
+ } else if (
605
+ isFluidError(origError) &&
606
+ // eslint-disable-next-line import/no-deprecated
607
+ origError.errorType === DriverErrorType.outOfStorageError
608
+ ) {
609
+ // If we get out of storage error from calling joinsession, then use the NoDeltaStream object so
610
+ // that user can at least load the container.
611
+ connection = new NoDeltaStream(undefined, {
612
+ text: origError.message,
613
+ error: origError,
614
+ });
593
615
  requestedMode = "read";
594
616
  break;
595
617
  }
@@ -660,8 +682,8 @@ export class ConnectionManager implements IConnectionManager {
660
682
  );
661
683
  }
662
684
 
663
- // Check for abort signal after while loop as well
664
- if (abortSignal.aborted === true) {
685
+ // Check for abort signal after while loop as well or we've been disposed
686
+ if (abortSignal.aborted === true || this._disposed) {
665
687
  connection.dispose();
666
688
  this.logger.sendTelemetryEvent({
667
689
  eventName: "ConnectionAttemptCancelled",
@@ -722,7 +744,7 @@ export class ConnectionManager implements IConnectionManager {
722
744
 
723
745
  // Remove listeners first so we don't try to retrigger this flow accidentally through reconnectOnError
724
746
  connection.off("op", this.opHandler);
725
- connection.off("signal", this.props.signalHandler);
747
+ connection.off("signal", this.signalHandler);
726
748
  connection.off("nack", this.nackHandler);
727
749
  connection.off("disconnect", this.disconnectHandlerInternal);
728
750
  connection.off("error", this.errorHandler);
@@ -805,7 +827,11 @@ export class ConnectionManager implements IConnectionManager {
805
827
  0x0e8 /* "readonly perf with write connection" */,
806
828
  );
807
829
 
808
- this.set_readonlyPermissions(readonly, oldReadonlyValue);
830
+ this.set_readonlyPermissions(
831
+ readonly,
832
+ oldReadonlyValue,
833
+ isNoDeltaStreamConnection(connection) ? connection.readonlyConnectionReason : undefined,
834
+ );
809
835
 
810
836
  if (this._disposed) {
811
837
  // Raise proper events, Log telemetry event and close connection.
@@ -816,7 +842,7 @@ export class ConnectionManager implements IConnectionManager {
816
842
  this._outbound.resume();
817
843
 
818
844
  connection.on("op", this.opHandler);
819
- connection.on("signal", this.props.signalHandler);
845
+ connection.on("signal", this.signalHandler);
820
846
  connection.on("nack", this.nackHandler);
821
847
  connection.on("disconnect", this.disconnectHandlerInternal);
822
848
  connection.on("error", this.errorHandler);
@@ -882,28 +908,32 @@ export class ConnectionManager implements IConnectionManager {
882
908
  type: SignalType.Clear,
883
909
  }),
884
910
  };
885
- this.props.signalHandler(clearSignal);
886
911
 
887
- for (const priorClient of connection.initialClients ?? []) {
888
- const joinSignal: ISignalMessage = {
912
+ // list of signals to process due to this new connection
913
+ let signalsToProcess: ISignalMessage[] = [clearSignal];
914
+
915
+ const clientJoinSignals: ISignalMessage[] = (connection.initialClients ?? []).map(
916
+ (priorClient) => ({
889
917
  clientId: null, // system signal
890
918
  content: JSON.stringify({
891
919
  type: SignalType.ClientJoin,
892
920
  content: priorClient, // ISignalClient
893
921
  }),
894
- };
895
- this.props.signalHandler(joinSignal);
922
+ }),
923
+ );
924
+ if (clientJoinSignals.length > 0) {
925
+ signalsToProcess = signalsToProcess.concat(clientJoinSignals);
896
926
  }
897
927
 
898
928
  // Unfortunately, there is no defined order between initialSignals (including join & leave signals)
899
929
  // and connection.initialClients. In practice, connection.initialSignals quite often contains join signal
900
930
  // for "self" and connection.initialClients does not contain "self", so we have to process them after
901
931
  // "clear" signal above.
902
- if (connection.initialSignals !== undefined) {
903
- for (const signal of connection.initialSignals) {
904
- this.props.signalHandler(signal);
905
- }
932
+ if (connection.initialSignals !== undefined && connection.initialSignals.length > 0) {
933
+ signalsToProcess = signalsToProcess.concat(connection.initialSignals);
906
934
  }
935
+
936
+ this.props.signalHandler(signalsToProcess);
907
937
  }
908
938
 
909
939
  /**
@@ -1121,6 +1151,11 @@ export class ConnectionManager implements IConnectionManager {
1121
1151
  this.props.incomingOpHandler(messages, "opHandler");
1122
1152
  };
1123
1153
 
1154
+ private readonly signalHandler = (signalsArg: ISignalMessage | ISignalMessage[]) => {
1155
+ const signals = Array.isArray(signalsArg) ? signalsArg : [signalsArg];
1156
+ this.props.signalHandler(signals);
1157
+ };
1158
+
1124
1159
  // Always connect in write mode after getting nacked.
1125
1160
  private readonly nackHandler = (documentId: string, messages: INack[]) => {
1126
1161
  const message = messages[0];
package/src/container.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  TelemetryEventCategory,
16
16
  IRequest,
17
17
  IResponse,
18
+ // eslint-disable-next-line import/no-deprecated
18
19
  IFluidRouter,
19
20
  FluidObject,
20
21
  LogLevel,
@@ -121,7 +122,6 @@ import { ConnectionManager } from "./connectionManager";
121
122
  import { ConnectionState } from "./connectionState";
122
123
  import {
123
124
  IProtocolHandler,
124
- OnlyValidTermValue,
125
125
  ProtocolHandler,
126
126
  ProtocolHandlerBuilder,
127
127
  protocolHandlerShouldProcessSignal,
@@ -134,6 +134,8 @@ const savedContainerEvent = "saved";
134
134
 
135
135
  const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
136
136
 
137
+ const hasBlobsSummaryTree = ".hasAttachmentBlobs";
138
+
137
139
  /**
138
140
  * @internal
139
141
  */
@@ -359,7 +361,6 @@ export interface IPendingContainerState {
359
361
  */
360
362
  savedOps: ISequencedDocumentMessage[];
361
363
  url: string;
362
- term: number;
363
364
  clientId?: string;
364
365
  }
365
366
 
@@ -474,7 +475,11 @@ export class Container
474
475
  container.mc.logger,
475
476
  { eventName: "RehydrateDetachedFromSnapshot" },
476
477
  async (_event) => {
477
- const deserializedSummary = JSON.parse(snapshot) as ISummaryTree;
478
+ const deserializedSummary = JSON.parse(snapshot);
479
+ if (!isCombinedAppAndProtocolSummary(deserializedSummary, hasBlobsSummaryTree)) {
480
+ throw new UsageError("Cannot rehydrate detached container. Incorrect format");
481
+ }
482
+
478
483
  await container.rehydrateDetachedFromSnapshot(deserializedSummary);
479
484
  return container;
480
485
  },
@@ -581,6 +586,7 @@ export class Container
581
586
  private readonly savedOps: ISequencedDocumentMessage[] = [];
582
587
  private baseSnapshot?: ISnapshotTree;
583
588
  private baseSnapshotBlobs?: ISerializableBlobContents;
589
+ private readonly _containerId: string;
584
590
 
585
591
  private lastVisible: number | undefined;
586
592
  private readonly visibilityEventHandler: (() => void) | undefined;
@@ -595,6 +601,10 @@ export class Container
595
601
  return this._deltaManager.connectionManager.connectionMode;
596
602
  }
597
603
 
604
+ /**
605
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
606
+ */
607
+ // eslint-disable-next-line import/no-deprecated
598
608
  public get IFluidRouter(): IFluidRouter {
599
609
  return this;
600
610
  }
@@ -619,7 +629,21 @@ export class Container
619
629
  }
620
630
 
621
631
  /**
622
- * Tracks host requiring read-only mode.
632
+ * Sends signal to runtime (and data stores) to be read-only.
633
+ * Hosts may have read only views, indicating to data stores that no edits are allowed.
634
+ * This is independent from this._readonlyPermissions (permissions) and this.connectionMode
635
+ * (server can return "write" mode even when asked for "read")
636
+ * Leveraging same "readonly" event as runtime & data stores should behave the same in such case
637
+ * as in read-only permissions.
638
+ * But this.active can be used by some DDSes to figure out if ops can be sent
639
+ * (for example, read-only view still participates in code proposals / upgrades decisions)
640
+ *
641
+ * Forcing Readonly does not prevent DDS from generating ops. It is up to user code to honour
642
+ * the readonly flag. If ops are generated, they will accumulate locally and not be sent. If
643
+ * there are pending in the outbound queue, it will stop sending until force readonly is
644
+ * cleared.
645
+ *
646
+ * @param readonly - set or clear force readonly.
623
647
  */
624
648
  public forceReadonly(readonly: boolean) {
625
649
  this._deltaManager.connectionManager.forceReadonly(readonly);
@@ -798,6 +822,8 @@ export class Container
798
822
  const clientType = `${interactive ? "interactive" : "noninteractive"}${
799
823
  type !== undefined && type !== "" ? `/${type}` : ""
800
824
  }`;
825
+
826
+ this._containerId = uuid();
801
827
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
802
828
  // We assign the id later so property getter is used.
803
829
  this.subLogger = createChildLogger({
@@ -805,7 +831,7 @@ export class Container
805
831
  properties: {
806
832
  all: {
807
833
  clientType, // Differentiating summarizer container from main container
808
- containerId: uuid(),
834
+ containerId: this._containerId,
809
835
  docId: () => this.resolvedUrl?.id,
810
836
  containerAttachState: () => this._attachState,
811
837
  containerLifecycleState: () => this._lifecycleState,
@@ -1137,7 +1163,6 @@ export class Container
1137
1163
  snapshotBlobs: this.baseSnapshotBlobs,
1138
1164
  savedOps: this.savedOps,
1139
1165
  url: this.resolvedUrl.url,
1140
- term: OnlyValidTermValue,
1141
1166
  // no need to save this if there is no pending runtime state
1142
1167
  clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
1143
1168
  };
@@ -1162,7 +1187,7 @@ export class Container
1162
1187
  const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1163
1188
 
1164
1189
  if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
1165
- combinedSummary.tree[".hasAttachmentBlobs"] = {
1190
+ combinedSummary.tree[hasBlobsSummaryTree] = {
1166
1191
  type: SummaryType.Blob,
1167
1192
  content: "true",
1168
1193
  };
@@ -1248,7 +1273,7 @@ export class Container
1248
1273
  }, // progress
1249
1274
  );
1250
1275
  }
1251
- await this.storageAdapter.connectToService(this.service);
1276
+ this.storageAdapter.connectToService(this.service);
1252
1277
 
1253
1278
  if (hasAttachmentBlobs) {
1254
1279
  // upload blobs to storage
@@ -1319,6 +1344,9 @@ export class Container
1319
1344
  );
1320
1345
  }
1321
1346
 
1347
+ /**
1348
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
1349
+ */
1322
1350
  public async request(path: IRequest): Promise<IResponse> {
1323
1351
  return PerformanceEvent.timedExecAsync(
1324
1352
  this.mc.logger,
@@ -1537,18 +1565,15 @@ export class Container
1537
1565
  this.client.details.type === summarizerClientType,
1538
1566
  );
1539
1567
 
1540
- // Ideally we always connect as "read" by default.
1541
- // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
1542
- // We should not rely on it by (one of them will address the issue, but we need to address both)
1543
- // 1) switching create new flow to one where we create file by posting snapshot
1544
- // 2) Fixing quorum workflows (have retry logic)
1545
- // That all said, "read" does not work with memorylicious workflows (that opens two simultaneous
1546
- // connections to same file) in two ways:
1547
- // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
1548
- // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
1568
+ // Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
1569
+ const mode =
1570
+ this.mc.config.getBoolean("Fluid.Container.ForceWriteConnection") === true ||
1571
+ (pendingLocalState?.savedOps.length ?? 0) > 0
1572
+ ? "write"
1573
+ : "read";
1549
1574
  const connectionArgs: IConnectionArgs = {
1550
1575
  reason: { text: "DocumentOpen" },
1551
- mode: "write",
1576
+ mode,
1552
1577
  fetchOpsFromStorage: false,
1553
1578
  };
1554
1579
 
@@ -1558,14 +1583,7 @@ export class Container
1558
1583
  this.connectToDeltaStream(connectionArgs);
1559
1584
  }
1560
1585
 
1561
- if (!pendingLocalState) {
1562
- await this.storageAdapter.connectToService(this.service);
1563
- } else {
1564
- // if we have pendingLocalState we can load without storage; don't wait for connection
1565
- this.storageAdapter.connectToService(this.service).catch((error) => {
1566
- this.close(error);
1567
- });
1568
- }
1586
+ this.storageAdapter.connectToService(this.service);
1569
1587
 
1570
1588
  this._attachState = AttachState.Attached;
1571
1589
 
@@ -1777,7 +1795,6 @@ export class Container
1777
1795
  private async createDetached(codeDetails: IFluidCodeDetails) {
1778
1796
  const attributes: IDocumentAttributes = {
1779
1797
  sequenceNumber: detachedContainerRefSeqNumber,
1780
- term: OnlyValidTermValue,
1781
1798
  minimumSequenceNumber: 0,
1782
1799
  };
1783
1800
 
@@ -1800,12 +1817,13 @@ export class Container
1800
1817
  }
1801
1818
 
1802
1819
  private async rehydrateDetachedFromSnapshot(detachedContainerSnapshot: ISummaryTree) {
1803
- if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
1820
+ if (detachedContainerSnapshot.tree[hasBlobsSummaryTree] !== undefined) {
1804
1821
  assert(
1805
1822
  !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1806
1823
  0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
1807
1824
  );
1808
- delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
1825
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1826
+ delete detachedContainerSnapshot.tree[hasBlobsSummaryTree];
1809
1827
  }
1810
1828
 
1811
1829
  const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
@@ -1843,7 +1861,6 @@ export class Container
1843
1861
  return {
1844
1862
  minimumSequenceNumber: 0,
1845
1863
  sequenceNumber: 0,
1846
- term: OnlyValidTermValue,
1847
1864
  };
1848
1865
  }
1849
1866
 
@@ -1993,6 +2010,7 @@ export class Container
1993
2010
  client.details.environment = [
1994
2011
  client.details.environment,
1995
2012
  ` loaderVersion:${pkgVersion}`,
2013
+ ` containerId:${this._containerId}`,
1996
2014
  ].join(";");
1997
2015
  return client;
1998
2016
  }
@@ -79,14 +79,14 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
79
79
  this.disposed = true;
80
80
  }
81
81
 
82
- public async connectToService(service: IDocumentService): Promise<void> {
82
+ public connectToService(service: IDocumentService): void {
83
83
  if (!(this._storageService instanceof BlobOnlyStorage)) {
84
84
  return;
85
85
  }
86
86
 
87
- const storageService = await service.connectToStorage();
87
+ const storageServiceP = service.connectToStorage();
88
88
  const retriableStorage = (this._storageService = new RetriableDocumentStorageService(
89
- storageService,
89
+ storageServiceP,
90
90
  this.logger,
91
91
  ));
92
92
 
@@ -99,12 +99,6 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
99
99
  this.addProtocolSummaryIfMissing,
100
100
  );
101
101
  }
102
-
103
- // ensure we did not lose that policy in the process of wrapping
104
- assert(
105
- storageService.policies?.minBlobSize === this._storageService.policies?.minBlobSize,
106
- 0x0e0 /* "lost minBlobSize policy" */,
107
- );
108
102
  }
109
103
 
110
104
  public loadSnapshotForRehydratingContainer(snapshotTree: ISnapshotTreeWithBlobContents) {
package/src/contracts.ts CHANGED
@@ -133,10 +133,10 @@ export interface IConnectionManagerFactoryArgs {
133
133
  readonly incomingOpHandler: (messages: ISequencedDocumentMessage[], reason: string) => void;
134
134
 
135
135
  /**
136
- * Called by connection manager for each incoming signals.
137
- * Maybe called before connectHandler is called (initial signals on socket connection)
136
+ * Called by connection manager for each incoming signal.
137
+ * May be called before connectHandler is called (due to initial signals on socket connection)
138
138
  */
139
- readonly signalHandler: (message: ISignalMessage) => void;
139
+ readonly signalHandler: (signals: ISignalMessage[]) => void;
140
140
 
141
141
  /**
142
142
  * Called when connection manager experiences delay in connecting to relay service.
@@ -179,8 +179,12 @@ export interface IConnectionManagerFactoryArgs {
179
179
  *
180
180
  * @param readonly - Whether or not the container is now read-only.
181
181
  * `undefined` indicates that user permissions are not yet known.
182
+ * @param readonlyConnectionReason - reason/error if any for the change
182
183
  */
183
- readonly readonlyChangeHandler: (readonly?: boolean) => void;
184
+ readonly readonlyChangeHandler: (
185
+ readonly?: boolean,
186
+ readonlyConnectionReason?: IConnectionStateChangeReason,
187
+ ) => void;
184
188
 
185
189
  /**
186
190
  * Called whenever we try to start establishing a new connection.
@@ -60,7 +60,10 @@ export class DebugLogger implements ITelemetryBaseLogger {
60
60
  });
61
61
  }
62
62
 
63
- private constructor(private readonly debug: IDebugger, private readonly debugErr: IDebugger) {}
63
+ private constructor(
64
+ private readonly debug: IDebugger,
65
+ private readonly debugErr: IDebugger,
66
+ ) {}
64
67
 
65
68
  /**
66
69
  * Send an event to debug loggers
@@ -108,6 +111,7 @@ export class DebugLogger implements ITelemetryBaseLogger {
108
111
  }
109
112
 
110
113
  // Print multi-line.
114
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
111
115
  logger(`${name} ${payload} ${tick} ${stack}`);
112
116
  }
113
117
  }
@@ -50,7 +50,6 @@ import {
50
50
  IConnectionStateChangeReason,
51
51
  } from "./contracts";
52
52
  import { DeltaQueue } from "./deltaQueue";
53
- import { OnlyValidTermValue } from "./protocol";
54
53
  import { ThrottlingWarning } from "./error";
55
54
 
56
55
  export interface IConnectionArgs {
@@ -115,17 +114,6 @@ function isClientMessage(message: ISequencedDocumentMessage | IDocumentMessage):
115
114
  }
116
115
  }
117
116
 
118
- /**
119
- * Type is used to cast AbortController to represent new version of DOM API and prevent build issues
120
- * TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
121
- */
122
- type AbortControllerReal = AbortController & { abort(reason?: any): void };
123
- /**
124
- * Type is used to cast AbortSignal to represent new version of DOM API and prevent build issues
125
- * TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
126
- */
127
- type AbortSignalReal = AbortSignal & { reason: any };
128
-
129
117
  /**
130
118
  * Manages the flow of both inbound and outbound messages. This class ensures that shared objects receive delta
131
119
  * messages in order regardless of possible network conditions or timings causing out of order delivery.
@@ -368,7 +356,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
368
356
  assert(this.connectionManager.connected, 0x238 /* "called only in connected state" */);
369
357
 
370
358
  const pendingSorted = this.pending.sort((a, b) => a.sequenceNumber - b.sequenceNumber);
371
- this.logger.sendErrorEvent({
359
+ this.logger.sendTelemetryEvent({
372
360
  ...event,
373
361
  // This directly tells us if fetching ops is in flight, and thus likely the reason of
374
362
  // stalled op processing
@@ -403,7 +391,11 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
403
391
  this.close(normalizeError(error));
404
392
  }
405
393
  },
406
- signalHandler: (message: ISignalMessage) => this._inboundSignal.push(message),
394
+ signalHandler: (signals: ISignalMessage[]) => {
395
+ for (const signal of signals) {
396
+ this._inboundSignal.push(signal);
397
+ }
398
+ },
407
399
  reconnectionDelayHandler: (delayMs: number, error: unknown) =>
408
400
  this.emitDelayInfo(this.deltaStreamDelayId, delayMs, error),
409
401
  closeHandler: (error: any) => this.close(error),
@@ -412,8 +404,12 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
412
404
  connectHandler: (connection: IConnectionDetailsInternal) =>
413
405
  this.connectHandler(connection),
414
406
  pongHandler: (latency: number) => this.emit("pong", latency),
415
- readonlyChangeHandler: (readonly?: boolean) =>
416
- safeRaiseEvent(this, this.logger, "readonly", readonly),
407
+ readonlyChangeHandler: (
408
+ readonly?: boolean,
409
+ readonlyConnectionReason?: IConnectionStateChangeReason,
410
+ ) => {
411
+ safeRaiseEvent(this, this.logger, "readonly", readonly, readonlyConnectionReason);
412
+ },
417
413
  establishConnectionHandler: (reason: IConnectionStateChangeReason) =>
418
414
  this.establishingConnection(reason),
419
415
  cancelConnectionHandler: (reason: IConnectionStateChangeReason) =>
@@ -660,8 +656,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
660
656
  // This is useless for known ranges (to is defined) as it means request is over either way.
661
657
  // And it will cancel unbound request too early, not allowing us to learn where the end of the file is.
662
658
  if (!opsFromFetch && cancelFetch(op)) {
663
- // TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
664
- (controller as AbortControllerReal).abort("DeltaManager getDeltas fetch cancelled");
659
+ controller.abort("DeltaManager getDeltas fetch cancelled");
665
660
  this._inbound.off("push", opListener);
666
661
  }
667
662
  };
@@ -670,10 +665,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
670
665
  this._inbound.on("push", opListener);
671
666
  assert(this.closeAbortController.signal.onabort === null, 0x1e8 /* "reentrancy" */);
672
667
  this.closeAbortController.signal.onabort = () =>
673
- // TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
674
- (controller as AbortControllerReal).abort(
675
- (this.closeAbortController.signal as AbortSignalReal).reason,
676
- );
668
+ controller.abort(this.closeAbortController.signal.reason);
677
669
 
678
670
  const stream = this.deltaStorage.fetchMessages(
679
671
  from, // inclusive
@@ -701,8 +693,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
701
693
  this.logger.sendTelemetryEvent({
702
694
  eventName: "DeltaManager_GetDeltasAborted",
703
695
  fetchReason,
704
- // TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
705
- reason: (controller.signal as AbortSignalReal).reason,
696
+ reason: controller.signal.reason,
706
697
  });
707
698
  }
708
699
  this.closeAbortController.signal.onabort = null;
@@ -759,8 +750,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
759
750
  }
760
751
 
761
752
  private clearQueues() {
762
- // TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
763
- (this.closeAbortController as AbortControllerReal).abort("DeltaManager is closed");
753
+ this.closeAbortController.abort("DeltaManager is closed");
764
754
 
765
755
  this._inbound.clear();
766
756
  this._inboundSignal.clear();
@@ -1059,11 +1049,6 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
1059
1049
  0x267 /* "lastObservedSeqNumber should be updated first" */,
1060
1050
  );
1061
1051
 
1062
- // Back-compat for older server with no term
1063
- if (message.term === undefined) {
1064
- message.term = OnlyValidTermValue;
1065
- }
1066
-
1067
1052
  if (this.handler === undefined) {
1068
1053
  throw new Error("Attempted to process an inbound message without a handler attached");
1069
1054
  }
package/src/index.ts CHANGED
@@ -15,4 +15,9 @@ export {
15
15
  Loader,
16
16
  requestResolvedObjectFromContainer,
17
17
  } from "./loader";
18
+ export {
19
+ isLocationRedirectionError,
20
+ resolveWithLocationRedirectionHandling,
21
+ } from "./location-redirection-utilities";
18
22
  export { IProtocolHandler, ProtocolHandlerBuilder } from "./protocol";
23
+ export { tryParseCompatibleResolvedUrl, IParsedUrl } from "./utils";