@fluidframework/container-loader 2.0.0-internal.6.4.0 → 2.0.0-internal.7.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 (126) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/api-extractor.json +9 -1
  3. package/api-report/container-loader.api.md +142 -0
  4. package/dist/catchUpMonitor.d.ts +2 -2
  5. package/dist/catchUpMonitor.d.ts.map +1 -1
  6. package/dist/connectionManager.d.ts +1 -0
  7. package/dist/connectionManager.d.ts.map +1 -1
  8. package/dist/connectionManager.js +109 -83
  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 +297 -0
  15. package/dist/container-loader-beta.d.ts +297 -0
  16. package/dist/container-loader-public.d.ts +297 -0
  17. package/dist/container-loader.d.ts +297 -0
  18. package/dist/container.d.ts +6 -1
  19. package/dist/container.d.ts.map +1 -1
  20. package/dist/container.js +215 -215
  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.map +1 -1
  25. package/dist/containerStorageAdapter.js +6 -8
  26. package/dist/containerStorageAdapter.js.map +1 -1
  27. package/dist/contracts.d.ts +5 -4
  28. package/dist/contracts.d.ts.map +1 -1
  29. package/dist/contracts.js +1 -1
  30. package/dist/contracts.js.map +1 -1
  31. package/dist/debugLogger.d.ts.map +1 -1
  32. package/dist/debugLogger.js +4 -4
  33. package/dist/debugLogger.js.map +1 -1
  34. package/dist/deltaManager.d.ts.map +1 -1
  35. package/dist/deltaManager.js +90 -89
  36. package/dist/deltaManager.js.map +1 -1
  37. package/dist/deltaQueue.js +14 -14
  38. package/dist/deltaQueue.js.map +1 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +4 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/loader.d.ts +3 -6
  44. package/dist/loader.d.ts.map +1 -1
  45. package/dist/loader.js +20 -85
  46. package/dist/loader.js.map +1 -1
  47. package/dist/location-redirection-utilities/index.d.ts +6 -0
  48. package/dist/location-redirection-utilities/index.d.ts.map +1 -0
  49. package/dist/location-redirection-utilities/index.js +11 -0
  50. package/dist/location-redirection-utilities/index.js.map +1 -0
  51. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
  52. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  53. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +51 -0
  54. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  55. package/dist/packageVersion.d.ts +1 -1
  56. package/dist/packageVersion.js +1 -1
  57. package/dist/packageVersion.js.map +1 -1
  58. package/dist/protocol.d.ts +1 -2
  59. package/dist/protocol.d.ts.map +1 -1
  60. package/dist/protocol.js +3 -5
  61. package/dist/protocol.js.map +1 -1
  62. package/dist/tsdoc-metadata.json +1 -1
  63. package/lib/catchUpMonitor.d.ts +2 -2
  64. package/lib/catchUpMonitor.d.ts.map +1 -1
  65. package/lib/connectionManager.d.ts +1 -0
  66. package/lib/connectionManager.d.ts.map +1 -1
  67. package/lib/connectionManager.js +112 -84
  68. package/lib/connectionManager.js.map +1 -1
  69. package/lib/connectionStateHandler.js +9 -9
  70. package/lib/connectionStateHandler.js.map +1 -1
  71. package/lib/container.d.ts +6 -1
  72. package/lib/container.d.ts.map +1 -1
  73. package/lib/container.js +216 -216
  74. package/lib/container.js.map +1 -1
  75. package/lib/containerContext.js +16 -16
  76. package/lib/containerContext.js.map +1 -1
  77. package/lib/containerStorageAdapter.d.ts.map +1 -1
  78. package/lib/containerStorageAdapter.js +6 -8
  79. package/lib/containerStorageAdapter.js.map +1 -1
  80. package/lib/contracts.d.ts +5 -4
  81. package/lib/contracts.d.ts.map +1 -1
  82. package/lib/contracts.js.map +1 -1
  83. package/lib/debugLogger.d.ts.map +1 -1
  84. package/lib/debugLogger.js +4 -4
  85. package/lib/debugLogger.js.map +1 -1
  86. package/lib/deltaManager.d.ts.map +1 -1
  87. package/lib/deltaManager.js +90 -89
  88. package/lib/deltaManager.js.map +1 -1
  89. package/lib/deltaQueue.js +14 -14
  90. package/lib/deltaQueue.js.map +1 -1
  91. package/lib/index.d.ts +1 -0
  92. package/lib/index.d.ts.map +1 -1
  93. package/lib/index.js +1 -0
  94. package/lib/index.js.map +1 -1
  95. package/lib/loader.d.ts +3 -6
  96. package/lib/loader.d.ts.map +1 -1
  97. package/lib/loader.js +20 -85
  98. package/lib/loader.js.map +1 -1
  99. package/lib/location-redirection-utilities/index.d.ts +6 -0
  100. package/lib/location-redirection-utilities/index.d.ts.map +1 -0
  101. package/lib/location-redirection-utilities/index.js +6 -0
  102. package/lib/location-redirection-utilities/index.js.map +1 -0
  103. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
  104. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  105. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +46 -0
  106. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  107. package/lib/packageVersion.d.ts +1 -1
  108. package/lib/packageVersion.js +1 -1
  109. package/lib/packageVersion.js.map +1 -1
  110. package/lib/protocol.d.ts +1 -2
  111. package/lib/protocol.d.ts.map +1 -1
  112. package/lib/protocol.js +1 -3
  113. package/lib/protocol.js.map +1 -1
  114. package/package.json +23 -24
  115. package/src/connectionManager.ts +57 -16
  116. package/src/container.ts +15 -15
  117. package/src/containerStorageAdapter.ts +0 -6
  118. package/src/contracts.ts +8 -4
  119. package/src/debugLogger.ts +4 -1
  120. package/src/deltaManager.ts +11 -9
  121. package/src/index.ts +4 -0
  122. package/src/loader.ts +24 -92
  123. package/src/location-redirection-utilities/index.ts +9 -0
  124. package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +59 -0
  125. package/src/packageVersion.ts +1 -1
  126. package/src/protocol.ts +2 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "2.0.0-internal.6.4.0",
3
+ "version": "2.0.0-internal.7.1.0",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -35,16 +35,15 @@
35
35
  "temp-directory": "nyc/.nyc_output"
36
36
  },
37
37
  "dependencies": {
38
- "@fluid-internal/client-utils": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
39
- "@fluidframework/container-definitions": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
40
- "@fluidframework/container-utils": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
41
- "@fluidframework/core-interfaces": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
42
- "@fluidframework/core-utils": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
43
- "@fluidframework/driver-definitions": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
44
- "@fluidframework/driver-utils": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
45
- "@fluidframework/protocol-base": "^1.0.1",
46
- "@fluidframework/protocol-definitions": "^1.1.0",
47
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
38
+ "@fluid-internal/client-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
39
+ "@fluidframework/container-definitions": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
40
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
41
+ "@fluidframework/core-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
42
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
43
+ "@fluidframework/driver-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
44
+ "@fluidframework/protocol-base": "^2.0.1",
45
+ "@fluidframework/protocol-definitions": "^3.0.0",
46
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
48
47
  "debug": "^4.1.1",
49
48
  "double-ended-queue": "^2.1.0-0",
50
49
  "events": "^3.1.0",
@@ -53,14 +52,14 @@
53
52
  "uuid": "^9.0.0"
54
53
  },
55
54
  "devDependencies": {
56
- "@fluid-internal/test-loader-utils": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
57
- "@fluid-tools/build-cli": "^0.22.0",
58
- "@fluidframework/build-common": "^2.0.0",
59
- "@fluidframework/build-tools": "^0.22.0",
60
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.6.3.0",
61
- "@fluidframework/eslint-config-fluid": "^2.1.0",
62
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.6.4.0 <2.0.0-internal.6.5.0",
63
- "@microsoft/api-extractor": "^7.34.4",
55
+ "@fluid-internal/test-loader-utils": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
56
+ "@fluid-tools/build-cli": "^0.25.0",
57
+ "@fluidframework/build-common": "^2.0.1",
58
+ "@fluidframework/build-tools": "^0.25.0",
59
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.7.0.0",
60
+ "@fluidframework/eslint-config-fluid": "^3.0.0",
61
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.1.0 <2.0.0-internal.7.2.0",
62
+ "@microsoft/api-extractor": "^7.37.0",
64
63
  "@types/double-ended-queue": "^2.1.0",
65
64
  "@types/events": "^3.0.0",
66
65
  "@types/lodash": "^4.14.118",
@@ -70,15 +69,15 @@
70
69
  "c8": "^7.7.1",
71
70
  "copyfiles": "^2.4.1",
72
71
  "cross-env": "^7.0.3",
73
- "eslint": "~8.6.0",
72
+ "eslint": "~8.50.0",
74
73
  "mocha": "^10.2.0",
75
74
  "mocha-json-output-reporter": "^2.0.1",
76
75
  "mocha-multi-reporters": "^1.5.1",
77
76
  "moment": "^2.21.0",
78
- "prettier": "~2.6.2",
77
+ "prettier": "~3.0.3",
79
78
  "rimraf": "^4.4.0",
80
79
  "sinon": "^7.4.2",
81
- "typescript": "~4.5.5"
80
+ "typescript": "~5.1.6"
82
81
  },
83
82
  "typeValidation": {
84
83
  "broken": {}
@@ -87,11 +86,11 @@
87
86
  "build": "fluid-build . --task build",
88
87
  "build:commonjs": "fluid-build . --task commonjs",
89
88
  "build:compile": "fluid-build . --task compile",
90
- "build:docs": "api-extractor run --local --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/doc-models/* ../../../_api-extractor-temp/",
89
+ "build:docs": "api-extractor run --local",
91
90
  "build:esnext": "tsc --project ./tsconfig.esnext.json",
92
91
  "build:genver": "gen-version",
93
92
  "build:test": "tsc --project ./src/test/tsconfig.json",
94
- "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
93
+ "ci:build:docs": "api-extractor run",
95
94
  "clean": "rimraf --glob 'dist' 'lib' '*.tsbuildinfo' '*.build.log' '_api-extractor-temp' 'nyc'",
96
95
  "eslint": "eslint --format stylish src",
97
96
  "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
@@ -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
 
@@ -473,10 +484,11 @@ export class ConnectionManager implements IConnectionManager {
473
484
  private set_readonlyPermissions(
474
485
  newReadonlyValue: boolean,
475
486
  oldReadonlyValue: boolean | undefined,
487
+ readonlyConnectionReason?: IConnectionStateChangeReason,
476
488
  ) {
477
489
  this._readonlyPermissions = newReadonlyValue;
478
490
  if (oldReadonlyValue !== this.readonly) {
479
- this.props.readonlyChangeHandler(this.readonly);
491
+ this.props.readonlyChangeHandler(this.readonly, readonlyConnectionReason);
480
492
  }
481
493
  }
482
494
 
@@ -575,7 +587,23 @@ export class ConnectionManager implements IConnectionManager {
575
587
  }
576
588
  } catch (origError: any) {
577
589
  if (isDeltaStreamConnectionForbiddenError(origError)) {
578
- connection = new NoDeltaStream(origError.storageOnlyReason);
590
+ connection = new NoDeltaStream(origError.storageOnlyReason, {
591
+ text: origError.message,
592
+ error: origError,
593
+ });
594
+ requestedMode = "read";
595
+ break;
596
+ } else if (
597
+ isFluidError(origError) &&
598
+ // eslint-disable-next-line import/no-deprecated
599
+ origError.errorType === DriverErrorType.outOfStorageError
600
+ ) {
601
+ // If we get out of storage error from calling joinsession, then use the NoDeltaStream object so
602
+ // that user can at least load the container.
603
+ connection = new NoDeltaStream(undefined, {
604
+ text: origError.message,
605
+ error: origError,
606
+ });
579
607
  requestedMode = "read";
580
608
  break;
581
609
  }
@@ -708,7 +736,7 @@ export class ConnectionManager implements IConnectionManager {
708
736
 
709
737
  // Remove listeners first so we don't try to retrigger this flow accidentally through reconnectOnError
710
738
  connection.off("op", this.opHandler);
711
- connection.off("signal", this.props.signalHandler);
739
+ connection.off("signal", this.signalHandler);
712
740
  connection.off("nack", this.nackHandler);
713
741
  connection.off("disconnect", this.disconnectHandlerInternal);
714
742
  connection.off("error", this.errorHandler);
@@ -791,7 +819,11 @@ export class ConnectionManager implements IConnectionManager {
791
819
  0x0e8 /* "readonly perf with write connection" */,
792
820
  );
793
821
 
794
- this.set_readonlyPermissions(readonly, oldReadonlyValue);
822
+ this.set_readonlyPermissions(
823
+ readonly,
824
+ oldReadonlyValue,
825
+ isNoDeltaStreamConnection(connection) ? connection.readonlyConnectionReason : undefined,
826
+ );
795
827
 
796
828
  if (this._disposed) {
797
829
  // Raise proper events, Log telemetry event and close connection.
@@ -802,7 +834,7 @@ export class ConnectionManager implements IConnectionManager {
802
834
  this._outbound.resume();
803
835
 
804
836
  connection.on("op", this.opHandler);
805
- connection.on("signal", this.props.signalHandler);
837
+ connection.on("signal", this.signalHandler);
806
838
  connection.on("nack", this.nackHandler);
807
839
  connection.on("disconnect", this.disconnectHandlerInternal);
808
840
  connection.on("error", this.errorHandler);
@@ -868,28 +900,32 @@ export class ConnectionManager implements IConnectionManager {
868
900
  type: SignalType.Clear,
869
901
  }),
870
902
  };
871
- this.props.signalHandler(clearSignal);
872
903
 
873
- for (const priorClient of connection.initialClients ?? []) {
874
- const joinSignal: ISignalMessage = {
904
+ // list of signals to process due to this new connection
905
+ let signalsToProcess: ISignalMessage[] = [clearSignal];
906
+
907
+ const clientJoinSignals: ISignalMessage[] = (connection.initialClients ?? []).map(
908
+ (priorClient) => ({
875
909
  clientId: null, // system signal
876
910
  content: JSON.stringify({
877
911
  type: SignalType.ClientJoin,
878
912
  content: priorClient, // ISignalClient
879
913
  }),
880
- };
881
- this.props.signalHandler(joinSignal);
914
+ }),
915
+ );
916
+ if (clientJoinSignals.length > 0) {
917
+ signalsToProcess = signalsToProcess.concat(clientJoinSignals);
882
918
  }
883
919
 
884
920
  // Unfortunately, there is no defined order between initialSignals (including join & leave signals)
885
921
  // and connection.initialClients. In practice, connection.initialSignals quite often contains join signal
886
922
  // for "self" and connection.initialClients does not contain "self", so we have to process them after
887
923
  // "clear" signal above.
888
- if (connection.initialSignals !== undefined) {
889
- for (const signal of connection.initialSignals) {
890
- this.props.signalHandler(signal);
891
- }
924
+ if (connection.initialSignals !== undefined && connection.initialSignals.length > 0) {
925
+ signalsToProcess = signalsToProcess.concat(connection.initialSignals);
892
926
  }
927
+
928
+ this.props.signalHandler(signalsToProcess);
893
929
  }
894
930
 
895
931
  /**
@@ -1107,6 +1143,11 @@ export class ConnectionManager implements IConnectionManager {
1107
1143
  this.props.incomingOpHandler(messages, "opHandler");
1108
1144
  };
1109
1145
 
1146
+ private readonly signalHandler = (signalsArg: ISignalMessage | ISignalMessage[]) => {
1147
+ const signals = Array.isArray(signalsArg) ? signalsArg : [signalsArg];
1148
+ this.props.signalHandler(signals);
1149
+ };
1150
+
1110
1151
  // Always connect in write mode after getting nacked.
1111
1152
  private readonly nackHandler = (documentId: string, messages: INack[]) => {
1112
1153
  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,
@@ -359,7 +359,6 @@ export interface IPendingContainerState {
359
359
  */
360
360
  savedOps: ISequencedDocumentMessage[];
361
361
  url: string;
362
- term: number;
363
362
  clientId?: string;
364
363
  }
365
364
 
@@ -595,6 +594,10 @@ export class Container
595
594
  return this._deltaManager.connectionManager.connectionMode;
596
595
  }
597
596
 
597
+ /**
598
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
599
+ */
600
+ // eslint-disable-next-line import/no-deprecated
598
601
  public get IFluidRouter(): IFluidRouter {
599
602
  return this;
600
603
  }
@@ -1151,7 +1154,6 @@ export class Container
1151
1154
  snapshotBlobs: this.baseSnapshotBlobs,
1152
1155
  savedOps: this.savedOps,
1153
1156
  url: this.resolvedUrl.url,
1154
- term: OnlyValidTermValue,
1155
1157
  // no need to save this if there is no pending runtime state
1156
1158
  clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
1157
1159
  };
@@ -1333,6 +1335,9 @@ export class Container
1333
1335
  );
1334
1336
  }
1335
1337
 
1338
+ /**
1339
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
1340
+ */
1336
1341
  public async request(path: IRequest): Promise<IResponse> {
1337
1342
  return PerformanceEvent.timedExecAsync(
1338
1343
  this.mc.logger,
@@ -1551,18 +1556,15 @@ export class Container
1551
1556
  this.client.details.type === summarizerClientType,
1552
1557
  );
1553
1558
 
1554
- // Ideally we always connect as "read" by default.
1555
- // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
1556
- // We should not rely on it by (one of them will address the issue, but we need to address both)
1557
- // 1) switching create new flow to one where we create file by posting snapshot
1558
- // 2) Fixing quorum workflows (have retry logic)
1559
- // That all said, "read" does not work with memorylicious workflows (that opens two simultaneous
1560
- // connections to same file) in two ways:
1561
- // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
1562
- // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
1559
+ // Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
1560
+ const mode =
1561
+ this.mc.config.getBoolean("Fluid.Container.ForceWriteConnection") === true ||
1562
+ (pendingLocalState?.savedOps.length ?? 0) > 0
1563
+ ? "write"
1564
+ : "read";
1563
1565
  const connectionArgs: IConnectionArgs = {
1564
1566
  reason: { text: "DocumentOpen" },
1565
- mode: "write",
1567
+ mode,
1566
1568
  fetchOpsFromStorage: false,
1567
1569
  };
1568
1570
 
@@ -1791,7 +1793,6 @@ export class Container
1791
1793
  private async createDetached(codeDetails: IFluidCodeDetails) {
1792
1794
  const attributes: IDocumentAttributes = {
1793
1795
  sequenceNumber: detachedContainerRefSeqNumber,
1794
- term: OnlyValidTermValue,
1795
1796
  minimumSequenceNumber: 0,
1796
1797
  };
1797
1798
 
@@ -1857,7 +1858,6 @@ export class Container
1857
1858
  return {
1858
1859
  minimumSequenceNumber: 0,
1859
1860
  sequenceNumber: 0,
1860
- term: OnlyValidTermValue,
1861
1861
  };
1862
1862
  }
1863
1863
 
@@ -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
@@ -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 {
@@ -403,7 +402,11 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
403
402
  this.close(normalizeError(error));
404
403
  }
405
404
  },
406
- signalHandler: (message: ISignalMessage) => this._inboundSignal.push(message),
405
+ signalHandler: (signals: ISignalMessage[]) => {
406
+ for (const signal of signals) {
407
+ this._inboundSignal.push(signal);
408
+ }
409
+ },
407
410
  reconnectionDelayHandler: (delayMs: number, error: unknown) =>
408
411
  this.emitDelayInfo(this.deltaStreamDelayId, delayMs, error),
409
412
  closeHandler: (error: any) => this.close(error),
@@ -412,8 +415,12 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
412
415
  connectHandler: (connection: IConnectionDetailsInternal) =>
413
416
  this.connectHandler(connection),
414
417
  pongHandler: (latency: number) => this.emit("pong", latency),
415
- readonlyChangeHandler: (readonly?: boolean) =>
416
- safeRaiseEvent(this, this.logger, "readonly", readonly),
418
+ readonlyChangeHandler: (
419
+ readonly?: boolean,
420
+ readonlyConnectionReason?: IConnectionStateChangeReason,
421
+ ) => {
422
+ safeRaiseEvent(this, this.logger, "readonly", readonly, readonlyConnectionReason);
423
+ },
417
424
  establishConnectionHandler: (reason: IConnectionStateChangeReason) =>
418
425
  this.establishingConnection(reason),
419
426
  cancelConnectionHandler: (reason: IConnectionStateChangeReason) =>
@@ -1059,11 +1066,6 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
1059
1066
  0x267 /* "lastObservedSeqNumber should be updated first" */,
1060
1067
  );
1061
1068
 
1062
- // Back-compat for older server with no term
1063
- if (message.term === undefined) {
1064
- message.term = OnlyValidTermValue;
1065
- }
1066
-
1067
1069
  if (this.handler === undefined) {
1068
1070
  throw new Error("Attempted to process an inbound message without a handler attached");
1069
1071
  }
package/src/index.ts CHANGED
@@ -15,4 +15,8 @@ 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";
package/src/loader.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  import {
18
18
  ITelemetryBaseLogger,
19
19
  FluidObject,
20
+ // eslint-disable-next-line import/no-deprecated
20
21
  IFluidRouter,
21
22
  IRequest,
22
23
  IRequestHeader,
@@ -45,10 +46,6 @@ import { pkgVersion } from "./packageVersion";
45
46
  import { ProtocolHandlerBuilder } from "./protocol";
46
47
  import { DebugLogger } from "./debugLogger";
47
48
 
48
- function canUseCache(request: IRequest): boolean {
49
- return request.headers?.[LoaderHeader.cache] === true;
50
- }
51
-
52
49
  function ensureResolvedUrlDefined(
53
50
  resolved: IResolvedUrl | undefined,
54
51
  ): asserts resolved is IResolvedUrl {
@@ -68,29 +65,26 @@ export class RelativeLoader implements ILoader {
68
65
  /**
69
66
  * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
70
67
  */
68
+ // eslint-disable-next-line import/no-deprecated
71
69
  public get IFluidRouter(): IFluidRouter {
72
70
  return this;
73
71
  }
74
72
 
75
73
  public async resolve(request: IRequest): Promise<IContainer> {
76
74
  if (request.url.startsWith("/")) {
77
- if (canUseCache(request)) {
78
- return this.container;
79
- } else {
80
- ensureResolvedUrlDefined(this.container.resolvedUrl);
81
- const container = await this.container.clone(
82
- {
83
- resolvedUrl: { ...this.container.resolvedUrl },
84
- version: request.headers?.[LoaderHeader.version] ?? undefined,
85
- loadMode: request.headers?.[LoaderHeader.loadMode],
86
- },
87
- {
88
- canReconnect: request.headers?.[LoaderHeader.reconnect],
89
- clientDetailsOverride: request.headers?.[LoaderHeader.clientDetails],
90
- },
91
- );
92
- return container;
93
- }
75
+ ensureResolvedUrlDefined(this.container.resolvedUrl);
76
+ const container = await this.container.clone(
77
+ {
78
+ resolvedUrl: { ...this.container.resolvedUrl },
79
+ version: request.headers?.[LoaderHeader.version] ?? undefined,
80
+ loadMode: request.headers?.[LoaderHeader.loadMode],
81
+ },
82
+ {
83
+ canReconnect: request.headers?.[LoaderHeader.reconnect],
84
+ clientDetailsOverride: request.headers?.[LoaderHeader.clientDetails],
85
+ },
86
+ );
87
+ return container;
94
88
  }
95
89
 
96
90
  if (this.loader === undefined) {
@@ -100,7 +94,7 @@ export class RelativeLoader implements ILoader {
100
94
  }
101
95
 
102
96
  /**
103
- * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
97
+ * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
104
98
  */
105
99
  public async request(request: IRequest): Promise<IResponse> {
106
100
  if (request.url.startsWith("/")) {
@@ -279,6 +273,7 @@ export type IDetachedBlobStorage = Pick<IDocumentStorageService, "createBlob" |
279
273
  * With an already-resolved container, we can request a component directly, without loading the container again
280
274
  * @param container - a resolved container
281
275
  * @returns component on the container
276
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
282
277
  */
283
278
  export async function requestResolvedObjectFromContainer(
284
279
  container: IContainer,
@@ -291,6 +286,7 @@ export async function requestResolvedObjectFromContainer(
291
286
  throw new Error(`Invalid URL ${container.resolvedUrl.url}`);
292
287
  }
293
288
 
289
+ // eslint-disable-next-line import/no-deprecated
294
290
  const entryPoint: FluidObject<IFluidRouter> | undefined = await container.getEntryPoint?.();
295
291
  const router = entryPoint?.IFluidRouter ?? container.IFluidRouter;
296
292
 
@@ -304,7 +300,6 @@ export async function requestResolvedObjectFromContainer(
304
300
  * Manages Fluid resource loading
305
301
  */
306
302
  export class Loader implements IHostLoader {
307
- private readonly containers = new Map<string, Promise<Container>>();
308
303
  public readonly services: ILoaderServices;
309
304
  private readonly mc: MonitoringContext;
310
305
 
@@ -354,6 +349,7 @@ export class Loader implements IHostLoader {
354
349
  /**
355
350
  * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
356
351
  */
352
+ // eslint-disable-next-line import/no-deprecated
357
353
  public get IFluidRouter(): IFluidRouter {
358
354
  return this;
359
355
  }
@@ -365,25 +361,13 @@ export class Loader implements IHostLoader {
365
361
  clientDetailsOverride?: IClientDetails;
366
362
  },
367
363
  ): Promise<IContainer> {
368
- const container = await Container.createDetached(
364
+ return Container.createDetached(
369
365
  {
370
366
  ...createDetachedProps,
371
367
  ...this.services,
372
368
  },
373
369
  codeDetails,
374
370
  );
375
-
376
- if (this.cachingEnabled) {
377
- container.once("attached", () => {
378
- ensureResolvedUrlDefined(container.resolvedUrl);
379
- const parsedUrl = parseUrl(container.resolvedUrl.url);
380
- if (parsedUrl !== undefined) {
381
- this.addToContainerCache(parsedUrl.id, Promise.resolve(container));
382
- }
383
- });
384
- }
385
-
386
- return container;
387
371
  }
388
372
 
389
373
  public async rehydrateDetachedContainerFromSnapshot(
@@ -430,37 +414,6 @@ export class Loader implements IHostLoader {
430
414
  );
431
415
  }
432
416
 
433
- private getKeyForContainerCache(request: IRequest, parsedUrl: IParsedUrl): string {
434
- const key =
435
- request.headers?.[LoaderHeader.version] !== undefined
436
- ? `${parsedUrl.id}@${request.headers[LoaderHeader.version]}`
437
- : parsedUrl.id;
438
- return key;
439
- }
440
-
441
- private addToContainerCache(key: string, containerP: Promise<Container>) {
442
- this.containers.set(key, containerP);
443
- containerP
444
- .then((container) => {
445
- // If the container is closed/disposed or becomes closed/disposed after we resolve it,
446
- // remove it from the cache.
447
- if (container.closed || container.disposed) {
448
- this.containers.delete(key);
449
- } else {
450
- container.once("closed", () => {
451
- this.containers.delete(key);
452
- });
453
- container.once("disposed", () => {
454
- this.containers.delete(key);
455
- });
456
- }
457
- })
458
- .catch((error) => {
459
- // If an error occured while resolving the container request, then remove it from the cache.
460
- this.containers.delete(key);
461
- });
462
- }
463
-
464
417
  private async resolveCore(
465
418
  request: IRequest,
466
419
  pendingLocalState?: IPendingContainerState,
@@ -489,11 +442,6 @@ export class Loader implements IHostLoader {
489
442
  // If set in both query string and headers, use query string. Also write the value from the query string into the header either way.
490
443
  request.headers[LoaderHeader.version] =
491
444
  parsed.version ?? request.headers[LoaderHeader.version];
492
- const cacheHeader = request.headers[LoaderHeader.cache];
493
- const canCache =
494
- // Take header value if present, else use ILoaderOptions.cache value
495
- (cacheHeader !== undefined ? cacheHeader === true : this.cachingEnabled) &&
496
- pendingLocalState === undefined;
497
445
  const fromSequenceNumber = request.headers[LoaderHeader.sequenceNumber] as
498
446
  | number
499
447
  | undefined;
@@ -513,26 +461,10 @@ export class Loader implements IHostLoader {
513
461
  throw new UsageError('opsBeforeReturn must be set to "sequenceNumber"');
514
462
  }
515
463
 
516
- let container: Container;
517
- if (canCache) {
518
- const key = this.getKeyForContainerCache(request, parsed);
519
- const maybeContainer = await this.containers.get(key);
520
- if (maybeContainer !== undefined) {
521
- container = maybeContainer;
522
- } else {
523
- const containerP = this.loadContainer(request, resolvedAsFluid);
524
- this.addToContainerCache(key, containerP);
525
- container = await containerP;
526
- }
527
- } else {
528
- container = await this.loadContainer(request, resolvedAsFluid, pendingLocalState);
529
- }
530
-
531
- return { container, parsed };
532
- }
533
-
534
- private get cachingEnabled() {
535
- return this.services.options.cache === true;
464
+ return {
465
+ container: await this.loadContainer(request, resolvedAsFluid, pendingLocalState),
466
+ parsed,
467
+ };
536
468
  }
537
469
 
538
470
  private async loadContainer(