@fluidframework/container-loader 2.0.0-dev.4.4.0.162574 → 2.0.0-dev.5.3.2.178189

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 (179) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +27 -3
  3. package/dist/catchUpMonitor.d.ts +1 -1
  4. package/dist/catchUpMonitor.d.ts.map +1 -1
  5. package/dist/catchUpMonitor.js.map +1 -1
  6. package/dist/connectionManager.d.ts +3 -2
  7. package/dist/connectionManager.d.ts.map +1 -1
  8. package/dist/connectionManager.js +32 -13
  9. package/dist/connectionManager.js.map +1 -1
  10. package/dist/connectionStateHandler.d.ts +18 -3
  11. package/dist/connectionStateHandler.d.ts.map +1 -1
  12. package/dist/connectionStateHandler.js +34 -9
  13. package/dist/connectionStateHandler.js.map +1 -1
  14. package/dist/container.d.ts +99 -70
  15. package/dist/container.d.ts.map +1 -1
  16. package/dist/container.js +260 -218
  17. package/dist/container.js.map +1 -1
  18. package/dist/containerContext.d.ts +24 -67
  19. package/dist/containerContext.d.ts.map +1 -1
  20. package/dist/containerContext.js +28 -217
  21. package/dist/containerContext.js.map +1 -1
  22. package/dist/containerStorageAdapter.d.ts +3 -3
  23. package/dist/containerStorageAdapter.d.ts.map +1 -1
  24. package/dist/containerStorageAdapter.js +29 -6
  25. package/dist/containerStorageAdapter.js.map +1 -1
  26. package/dist/contracts.d.ts +9 -3
  27. package/dist/contracts.d.ts.map +1 -1
  28. package/dist/contracts.js.map +1 -1
  29. package/dist/deltaManager.d.ts +22 -9
  30. package/dist/deltaManager.d.ts.map +1 -1
  31. package/dist/deltaManager.js +42 -31
  32. package/dist/deltaManager.js.map +1 -1
  33. package/dist/deltaQueue.d.ts +2 -3
  34. package/dist/deltaQueue.d.ts.map +1 -1
  35. package/dist/deltaQueue.js +2 -3
  36. package/dist/deltaQueue.js.map +1 -1
  37. package/dist/disposal.d.ts +13 -0
  38. package/dist/disposal.d.ts.map +1 -0
  39. package/dist/disposal.js +25 -0
  40. package/dist/disposal.js.map +1 -0
  41. package/dist/index.d.ts +1 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/loader.d.ts +9 -8
  45. package/dist/loader.d.ts.map +1 -1
  46. package/dist/loader.js +47 -61
  47. package/dist/loader.js.map +1 -1
  48. package/dist/noopHeuristic.d.ts +23 -0
  49. package/dist/noopHeuristic.d.ts.map +1 -0
  50. package/dist/{collabWindowTracker.js → noopHeuristic.js} +30 -42
  51. package/dist/noopHeuristic.js.map +1 -0
  52. package/dist/packageVersion.d.ts +1 -1
  53. package/dist/packageVersion.js +1 -1
  54. package/dist/packageVersion.js.map +1 -1
  55. package/dist/protocol.d.ts +7 -12
  56. package/dist/protocol.d.ts.map +1 -1
  57. package/dist/protocol.js +17 -19
  58. package/dist/protocol.js.map +1 -1
  59. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  60. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  61. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  62. package/dist/quorum.d.ts +1 -17
  63. package/dist/quorum.d.ts.map +1 -1
  64. package/dist/quorum.js +1 -17
  65. package/dist/quorum.js.map +1 -1
  66. package/dist/retriableDocumentStorageService.d.ts +3 -2
  67. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  68. package/dist/retriableDocumentStorageService.js.map +1 -1
  69. package/dist/tsdoc-metadata.json +11 -0
  70. package/dist/utils.d.ts +2 -0
  71. package/dist/utils.d.ts.map +1 -1
  72. package/dist/utils.js +8 -1
  73. package/dist/utils.js.map +1 -1
  74. package/lib/catchUpMonitor.d.ts +1 -1
  75. package/lib/catchUpMonitor.d.ts.map +1 -1
  76. package/lib/catchUpMonitor.js.map +1 -1
  77. package/lib/connectionManager.d.ts +3 -2
  78. package/lib/connectionManager.d.ts.map +1 -1
  79. package/lib/connectionManager.js +33 -14
  80. package/lib/connectionManager.js.map +1 -1
  81. package/lib/connectionStateHandler.d.ts +18 -3
  82. package/lib/connectionStateHandler.d.ts.map +1 -1
  83. package/lib/connectionStateHandler.js +35 -10
  84. package/lib/connectionStateHandler.js.map +1 -1
  85. package/lib/container.d.ts +99 -70
  86. package/lib/container.d.ts.map +1 -1
  87. package/lib/container.js +264 -222
  88. package/lib/container.js.map +1 -1
  89. package/lib/containerContext.d.ts +24 -67
  90. package/lib/containerContext.d.ts.map +1 -1
  91. package/lib/containerContext.js +28 -217
  92. package/lib/containerContext.js.map +1 -1
  93. package/lib/containerStorageAdapter.d.ts +3 -3
  94. package/lib/containerStorageAdapter.d.ts.map +1 -1
  95. package/lib/containerStorageAdapter.js +29 -6
  96. package/lib/containerStorageAdapter.js.map +1 -1
  97. package/lib/contracts.d.ts +9 -3
  98. package/lib/contracts.d.ts.map +1 -1
  99. package/lib/contracts.js.map +1 -1
  100. package/lib/deltaManager.d.ts +22 -9
  101. package/lib/deltaManager.d.ts.map +1 -1
  102. package/lib/deltaManager.js +44 -33
  103. package/lib/deltaManager.js.map +1 -1
  104. package/lib/deltaQueue.d.ts +2 -3
  105. package/lib/deltaQueue.d.ts.map +1 -1
  106. package/lib/deltaQueue.js +2 -3
  107. package/lib/deltaQueue.js.map +1 -1
  108. package/lib/disposal.d.ts +13 -0
  109. package/lib/disposal.d.ts.map +1 -0
  110. package/lib/disposal.js +21 -0
  111. package/lib/disposal.js.map +1 -0
  112. package/lib/index.d.ts +1 -2
  113. package/lib/index.d.ts.map +1 -1
  114. package/lib/index.js +1 -1
  115. package/lib/index.js.map +1 -1
  116. package/lib/loader.d.ts +9 -8
  117. package/lib/loader.d.ts.map +1 -1
  118. package/lib/loader.js +47 -61
  119. package/lib/loader.js.map +1 -1
  120. package/lib/noopHeuristic.d.ts +23 -0
  121. package/lib/noopHeuristic.d.ts.map +1 -0
  122. package/lib/{collabWindowTracker.js → noopHeuristic.js} +30 -42
  123. package/lib/noopHeuristic.js.map +1 -0
  124. package/lib/packageVersion.d.ts +1 -1
  125. package/lib/packageVersion.js +1 -1
  126. package/lib/packageVersion.js.map +1 -1
  127. package/lib/protocol.d.ts +7 -12
  128. package/lib/protocol.d.ts.map +1 -1
  129. package/lib/protocol.js +15 -18
  130. package/lib/protocol.js.map +1 -1
  131. package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
  132. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  133. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  134. package/lib/quorum.d.ts +1 -17
  135. package/lib/quorum.d.ts.map +1 -1
  136. package/lib/quorum.js +1 -16
  137. package/lib/quorum.js.map +1 -1
  138. package/lib/retriableDocumentStorageService.d.ts +3 -2
  139. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  140. package/lib/retriableDocumentStorageService.js.map +1 -1
  141. package/lib/utils.d.ts +2 -0
  142. package/lib/utils.d.ts.map +1 -1
  143. package/lib/utils.js +7 -1
  144. package/lib/utils.js.map +1 -1
  145. package/package.json +22 -20
  146. package/src/catchUpMonitor.ts +1 -1
  147. package/src/connectionManager.ts +40 -22
  148. package/src/connectionStateHandler.ts +66 -17
  149. package/src/container.ts +464 -292
  150. package/src/containerContext.ts +33 -341
  151. package/src/containerStorageAdapter.ts +40 -10
  152. package/src/contracts.ts +11 -3
  153. package/src/deltaManager.ts +74 -45
  154. package/src/deltaQueue.ts +2 -3
  155. package/src/disposal.ts +25 -0
  156. package/src/index.ts +1 -8
  157. package/src/loader.ts +85 -83
  158. package/src/{collabWindowTracker.ts → noopHeuristic.ts} +37 -47
  159. package/src/packageVersion.ts +1 -1
  160. package/src/protocol.ts +18 -39
  161. package/src/protocolTreeDocumentStorageService.ts +1 -1
  162. package/src/quorum.ts +2 -31
  163. package/src/retriableDocumentStorageService.ts +4 -2
  164. package/src/utils.ts +15 -1
  165. package/dist/collabWindowTracker.d.ts +0 -19
  166. package/dist/collabWindowTracker.d.ts.map +0 -1
  167. package/dist/collabWindowTracker.js.map +0 -1
  168. package/dist/deltaManagerProxy.d.ts +0 -42
  169. package/dist/deltaManagerProxy.d.ts.map +0 -1
  170. package/dist/deltaManagerProxy.js +0 -79
  171. package/dist/deltaManagerProxy.js.map +0 -1
  172. package/lib/collabWindowTracker.d.ts +0 -19
  173. package/lib/collabWindowTracker.d.ts.map +0 -1
  174. package/lib/collabWindowTracker.js.map +0 -1
  175. package/lib/deltaManagerProxy.d.ts +0 -42
  176. package/lib/deltaManagerProxy.d.ts.map +0 -1
  177. package/lib/deltaManagerProxy.js +0 -74
  178. package/lib/deltaManagerProxy.js.map +0 -1
  179. package/src/deltaManagerProxy.ts +0 -109
package/lib/container.js CHANGED
@@ -5,30 +5,30 @@
5
5
  // eslint-disable-next-line import/no-internal-modules
6
6
  import merge from "lodash/merge";
7
7
  import { v4 as uuid } from "uuid";
8
- import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
8
+ import { TypedEventEmitter, assert, performance, unreachableCase, } from "@fluidframework/common-utils";
9
9
  import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
10
10
  import { GenericError, UsageError } from "@fluidframework/container-utils";
11
- import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, isCombinedAppAndProtocolSummary, } from "@fluidframework/driver-utils";
11
+ import { readAndParse, OnlineStatus, isOnline, combineAppAndProtocolSummary, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, canBeCoalescedByService, } from "@fluidframework/driver-utils";
12
12
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
13
13
  import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
14
14
  import { Audience } from "./audience";
15
15
  import { ContainerContext } from "./containerContext";
16
16
  import { ReconnectMode, getPackageName } from "./contracts";
17
17
  import { DeltaManager } from "./deltaManager";
18
- import { DeltaManagerProxy } from "./deltaManagerProxy";
19
18
  import { RelativeLoader } from "./loader";
20
19
  import { pkgVersion } from "./packageVersion";
21
20
  import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTreeWithBlobContents, } from "./containerStorageAdapter";
22
21
  import { createConnectionStateHandler } from "./connectionStateHandler";
23
22
  import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
24
- import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy, } from "./quorum";
25
- import { CollabWindowTracker } from "./collabWindowTracker";
23
+ import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
24
+ import { NoopHeuristic } from "./noopHeuristic";
26
25
  import { ConnectionManager } from "./connectionManager";
27
26
  import { ConnectionState } from "./connectionState";
28
- import { OnlyValidTermValue, ProtocolHandler, } from "./protocol";
27
+ import { OnlyValidTermValue, ProtocolHandler, protocolHandlerShouldProcessSignal, } from "./protocol";
29
28
  const detachedContainerRefSeqNumber = 0;
30
29
  const dirtyContainerEvent = "dirty";
31
30
  const savedContainerEvent = "saved";
31
+ const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
32
32
  /**
33
33
  * Waits until container connects to delta storage and gets up-to-date.
34
34
  *
@@ -119,26 +119,18 @@ export async function ReportIfTooLong(logger, eventName, action) {
119
119
  }
120
120
  }
121
121
  const summarizerClientType = "summarizer";
122
- /**
123
- * @deprecated - In the next release Container will no longer be exported, IContainer should be used in its place.
124
- */
125
122
  export class Container extends EventEmitterWithErrorHandling {
126
123
  /**
127
124
  * @internal
128
125
  */
129
- constructor(loader, config, protocolHandlerBuilder) {
130
- var _a, _b, _c;
126
+ constructor(createProps, loadProps) {
127
+ var _a;
131
128
  super((name, error) => {
132
129
  this.mc.logger.sendErrorEvent({
133
130
  eventName: "ContainerEventHandlerException",
134
131
  name: typeof name === "string" ? name : undefined,
135
132
  }, error);
136
133
  });
137
- this.loader = loader;
138
- this.protocolHandlerBuilder = protocolHandlerBuilder;
139
- // Tells if container can reconnect on losing fist connection
140
- // If false, container gets closed on loss of connection.
141
- this._canReconnect = true;
142
134
  /**
143
135
  * Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
144
136
  *
@@ -164,28 +156,58 @@ export class Container extends EventEmitterWithErrorHandling {
164
156
  this.attachStarted = false;
165
157
  this._dirtyContainer = false;
166
158
  this.savedOps = [];
159
+ this.clientsWhoShouldHaveLeft = new Set();
167
160
  this.setAutoReconnectTime = performance.now();
161
+ this._lifecycleEvents = new TypedEventEmitter();
168
162
  this._disposed = false;
169
- this.clientDetailsOverride = config.clientDetailsOverride;
170
- this._resolvedUrl = config.resolvedUrl;
171
- if (config.canReconnect !== undefined) {
172
- this._canReconnect = config.canReconnect;
173
- }
163
+ this.getAbsoluteUrl = async (relativeUrl) => {
164
+ if (this.resolvedUrl === undefined) {
165
+ return undefined;
166
+ }
167
+ return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName(this._loadedCodeDetails));
168
+ };
169
+ this.updateDirtyContainerState = (dirty) => {
170
+ if (this._dirtyContainer === dirty) {
171
+ return;
172
+ }
173
+ this._dirtyContainer = dirty;
174
+ this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
175
+ };
176
+ const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
177
+ this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
178
+ const pendingLocalState = loadProps === null || loadProps === void 0 ? void 0 : loadProps.pendingLocalState;
179
+ this._canReconnect = canReconnect !== null && canReconnect !== void 0 ? canReconnect : true;
180
+ this.clientDetailsOverride = clientDetailsOverride;
181
+ this.urlResolver = urlResolver;
182
+ this.serviceFactory = documentServiceFactory;
183
+ this.codeLoader = codeLoader;
184
+ // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
185
+ // all clients that were loaded from the same loader (including summarizer clients).
186
+ // Tracking alternative ways to handle this in AB#4129.
187
+ this.options = Object.assign({}, options);
188
+ this.scope = scope;
189
+ this.detachedBlobStorage = detachedBlobStorage;
190
+ this.protocolHandlerBuilder =
191
+ protocolHandlerBuilder !== null && protocolHandlerBuilder !== void 0 ? protocolHandlerBuilder : ((...args) => new ProtocolHandler(...args, new Audience()));
192
+ // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
193
+ this.clone = async (_loadProps, createParamOverrides) => {
194
+ return Container.load(_loadProps, Object.assign(Object.assign({}, createProps), createParamOverrides));
195
+ };
174
196
  // Create logger for data stores to use
175
197
  const type = this.client.details.type;
176
198
  const interactive = this.client.details.capabilities.interactive;
177
199
  const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
178
200
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
179
201
  // We assign the id later so property getter is used.
180
- this.subLogger = ChildLogger.create(loader.services.subLogger, undefined, {
202
+ this.subLogger = ChildLogger.create(subLogger, undefined, {
181
203
  all: {
182
204
  clientType,
183
205
  containerId: uuid(),
184
- docId: () => { var _a, _b; return (_b = (_a = this._resolvedUrl) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : undefined; },
206
+ docId: () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; },
185
207
  containerAttachState: () => this._attachState,
186
208
  containerLifecycleState: () => this._lifecycleState,
187
209
  containerConnectionState: () => ConnectionState[this.connectionState],
188
- serializedContainer: config.serializedContainerState !== undefined,
210
+ serializedContainer: pendingLocalState !== undefined,
189
211
  },
190
212
  // we need to be judicious with our logging here to avoid generating too much data
191
213
  // all data logged here should be broadly applicable, and not specific to a
@@ -195,23 +217,24 @@ export class Container extends EventEmitterWithErrorHandling {
195
217
  dmInitialSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.initialSequenceNumber; },
196
218
  dmLastProcessedSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastSequenceNumber; },
197
219
  dmLastKnownSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastKnownSeqNumber; },
198
- containerLoadedFromVersionId: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
199
- containerLoadedFromVersionDate: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
220
+ containerLoadedFromVersionId: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
221
+ containerLoadedFromVersionDate: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
200
222
  // message information to associate errors with the specific execution state
201
223
  // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
202
224
  dmLastMsqSeqNumber: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.sequenceNumber; },
203
225
  dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
204
- dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
226
+ dmLastMsqSeqClientId: () => {
227
+ var _a, _b, _c, _d;
228
+ return ((_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId) === null
229
+ ? "null"
230
+ : (_d = (_c = this.deltaManager) === null || _c === void 0 ? void 0 : _c.lastMessage) === null || _d === void 0 ? void 0 : _d.clientId;
231
+ },
205
232
  dmLastMsgClientSeq: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientSequenceNumber; },
206
233
  connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
207
234
  },
208
235
  });
209
236
  // Prefix all events in this file with container-loader
210
237
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
211
- // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
212
- // all clients that were loaded from the same loader (including summarizer clients).
213
- // Tracking alternative ways to handle this in AB#4129.
214
- this.options = Object.assign({}, this.loader.services.options);
215
238
  this._deltaManager = this.createDeltaManager();
216
239
  this.connectionStateHandler = createConnectionStateHandler({
217
240
  logger: this.mc.logger,
@@ -227,7 +250,7 @@ export class Container extends EventEmitterWithErrorHandling {
227
250
  }
228
251
  },
229
252
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
230
- maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
253
+ maxClientLeaveWaitTime: options.maxClientLeaveWaitTime,
231
254
  logConnectionIssue: (eventName, category, details) => {
232
255
  const mode = this.connectionMode;
233
256
  // We get here when socket does not receive any ops on "write" connection, including
@@ -251,7 +274,10 @@ export class Container extends EventEmitterWithErrorHandling {
251
274
  this.connect();
252
275
  }
253
276
  },
254
- }, this.deltaManager, (_a = config.serializedContainerState) === null || _a === void 0 ? void 0 : _a.clientId);
277
+ clientShouldHaveLeft: (clientId) => {
278
+ this.clientsWhoShouldHaveLeft.add(clientId);
279
+ },
280
+ }, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
255
281
  this.on(savedContainerEvent, () => {
256
282
  this.connectionStateHandler.containerSaved();
257
283
  });
@@ -263,8 +289,8 @@ export class Container extends EventEmitterWithErrorHandling {
263
289
  : combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
264
290
  // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
265
291
  // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
266
- const forceEnableSummarizeProtocolTree = (_b = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _b !== void 0 ? _b : this.loader.services.options.summarizeProtocolTree;
267
- this.storageAdapter = new ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, (_c = config.serializedContainerState) === null || _c === void 0 ? void 0 : _c.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
292
+ const forceEnableSummarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _a !== void 0 ? _a : options.summarizeProtocolTree;
293
+ this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
268
294
  const isDomAvailable = typeof document === "object" &&
269
295
  document !== null &&
270
296
  typeof document.addEventListener === "function" &&
@@ -290,33 +316,28 @@ export class Container extends EventEmitterWithErrorHandling {
290
316
  * Load an existing container.
291
317
  * @internal
292
318
  */
293
- static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
294
- const container = new Container(loader, {
295
- clientDetailsOverride: loadOptions.clientDetailsOverride,
296
- resolvedUrl: loadOptions.resolvedUrl,
297
- canReconnect: loadOptions.canReconnect,
298
- serializedContainerState: pendingLocalState,
299
- }, protocolHandlerBuilder);
319
+ static async load(loadProps, createProps) {
320
+ const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
321
+ const container = new Container(createProps, loadProps);
322
+ const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
300
323
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
301
- var _a, _b;
302
- const version = loadOptions.version;
303
324
  const defaultMode = { opsBeforeReturn: "cached" };
304
325
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
305
326
  // to return container, so ignore this value and use undefined for opsBeforeReturn
306
327
  const mode = pendingLocalState
307
- ? Object.assign(Object.assign({}, ((_a = loadOptions.loadMode) !== null && _a !== void 0 ? _a : defaultMode)), { opsBeforeReturn: undefined }) : (_b = loadOptions.loadMode) !== null && _b !== void 0 ? _b : defaultMode;
328
+ ? Object.assign(Object.assign({}, (loadMode !== null && loadMode !== void 0 ? loadMode : defaultMode)), { opsBeforeReturn: undefined }) : loadMode !== null && loadMode !== void 0 ? loadMode : defaultMode;
308
329
  const onClosed = (err) => {
309
330
  // pre-0.58 error message: containerClosedWithoutErrorDuringLoad
310
331
  reject(err !== null && err !== void 0 ? err : new GenericError("Container closed without error during load"));
311
332
  };
312
333
  container.on("closed", onClosed);
313
334
  container
314
- .load(version, mode, pendingLocalState)
335
+ .load(version, mode, resolvedUrl, pendingLocalState)
315
336
  .finally(() => {
316
337
  container.removeListener("closed", onClosed);
317
338
  })
318
339
  .then((props) => {
319
- event.end(Object.assign(Object.assign({}, props), loadOptions.loadMode));
340
+ event.end(Object.assign(Object.assign({}, props), loadMode));
320
341
  resolve(container);
321
342
  }, (error) => {
322
343
  const err = normalizeError(error);
@@ -326,13 +347,13 @@ export class Container extends EventEmitterWithErrorHandling {
326
347
  container.close(err);
327
348
  onClosed(err);
328
349
  });
329
- }), { start: true, end: true, cancel: "generic" });
350
+ }), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
330
351
  }
331
352
  /**
332
353
  * Create a new container in a detached state.
333
354
  */
334
- static async createDetached(loader, codeDetails, protocolHandlerBuilder) {
335
- const container = new Container(loader, {}, protocolHandlerBuilder);
355
+ static async createDetached(createProps, codeDetails) {
356
+ const container = new Container(createProps);
336
357
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
337
358
  await container.createDetached(codeDetails);
338
359
  return container;
@@ -342,8 +363,8 @@ export class Container extends EventEmitterWithErrorHandling {
342
363
  * Create a new container in a detached state that is initialized with a
343
364
  * snapshot from a previous detached container.
344
365
  */
345
- static async rehydrateDetachedFromSnapshot(loader, snapshot, protocolHandlerBuilder) {
346
- const container = new Container(loader, {}, protocolHandlerBuilder);
366
+ static async rehydrateDetachedFromSnapshot(createProps, snapshot) {
367
+ const container = new Container(createProps);
347
368
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
348
369
  const deserializedSummary = JSON.parse(snapshot);
349
370
  await container.rehydrateDetachedFromSnapshot(deserializedSummary);
@@ -365,14 +386,11 @@ export class Container extends EventEmitterWithErrorHandling {
365
386
  this._lifecycleState === "disposing" ||
366
387
  this._lifecycleState === "disposed");
367
388
  }
368
- get storage() {
369
- return this.storageAdapter;
370
- }
371
- get context() {
372
- if (this._context === undefined) {
373
- throw new GenericError("Attempted to access context before it was defined");
389
+ get runtime() {
390
+ if (this._runtime === undefined) {
391
+ throw new Error("Attempted to access runtime before it was defined");
374
392
  }
375
- return this._context;
393
+ return this._runtime;
376
394
  }
377
395
  get protocolHandler() {
378
396
  if (this._protocolHandler === undefined) {
@@ -387,17 +405,23 @@ export class Container extends EventEmitterWithErrorHandling {
387
405
  return this;
388
406
  }
389
407
  get resolvedUrl() {
390
- return this._resolvedUrl;
391
- }
392
- get loadedFromVersion() {
393
- return this._loadedFromVersion;
408
+ var _a;
409
+ /**
410
+ * All attached containers will have a document service,
411
+ * this is required, as attached containers are attached to
412
+ * a service. Detached containers will neither have a document
413
+ * service or a resolved url as they only exist locally.
414
+ * in order to create a document service a resolved url must
415
+ * first be obtained, this is how the container is identified.
416
+ * Because of this, the document service's resolved url
417
+ * is always the same as the containers, as we had to
418
+ * obtain the resolved url, and then create the service from it.
419
+ */
420
+ return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
394
421
  }
395
422
  get readOnlyInfo() {
396
423
  return this._deltaManager.readOnlyInfo;
397
424
  }
398
- get closeSignal() {
399
- return this._deltaManager.closeAbortController.signal;
400
- }
401
425
  /**
402
426
  * Tracks host requiring read-only mode.
403
427
  */
@@ -413,13 +437,6 @@ export class Container extends EventEmitterWithErrorHandling {
413
437
  get connected() {
414
438
  return this.connectionStateHandler.connectionState === ConnectionState.Connected;
415
439
  }
416
- /**
417
- * Service configuration details. If running in offline mode will be undefined otherwise will contain service
418
- * configuration details returned as part of the initial connection.
419
- */
420
- get serviceConfiguration() {
421
- return this._deltaManager.serviceConfiguration;
422
- }
423
440
  /**
424
441
  * The server provided id of the client.
425
442
  * Set once this.connected is true, otherwise undefined
@@ -427,21 +444,11 @@ export class Container extends EventEmitterWithErrorHandling {
427
444
  get clientId() {
428
445
  return this._clientId;
429
446
  }
430
- /**
431
- * The server provided claims of the client.
432
- * Set once this.connected is true, otherwise undefined
433
- */
434
- get scopes() {
435
- return this._deltaManager.connectionManager.scopes;
436
- }
437
- get clientDetails() {
438
- return this._deltaManager.clientDetails;
439
- }
440
447
  get offlineLoadEnabled() {
441
448
  var _a, _b;
442
449
  const enabled = (_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : ((_b = this.options) === null || _b === void 0 ? void 0 : _b.enableOfflineLoad) === true;
443
450
  // summarizer will not have any pending state we want to save
444
- return enabled && this.clientDetails.capabilities.interactive;
451
+ return enabled && this.deltaManager.clientDetails.capabilities.interactive;
445
452
  }
446
453
  /**
447
454
  * Get the code details that are currently specified for the container.
@@ -456,8 +463,7 @@ export class Container extends EventEmitterWithErrorHandling {
456
463
  * loaded.
457
464
  */
458
465
  getLoadedCodeDetails() {
459
- var _a;
460
- return (_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails;
466
+ return this._loadedCodeDetails;
461
467
  }
462
468
  /**
463
469
  * Retrieves the audience associated with the document
@@ -473,49 +479,31 @@ export class Container extends EventEmitterWithErrorHandling {
473
479
  get isDirty() {
474
480
  return this._dirtyContainer;
475
481
  }
476
- get serviceFactory() {
477
- return this.loader.services.documentServiceFactory;
478
- }
479
- get urlResolver() {
480
- return this.loader.services.urlResolver;
481
- }
482
- get scope() {
483
- return this.loader.services.scope;
484
- }
485
- get codeLoader() {
486
- return this.loader.services.codeLoader;
487
- }
488
482
  /**
489
483
  * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
490
484
  */
491
485
  async getEntryPoint() {
492
486
  var _a, _b;
493
- // Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
494
- // allow it since they mean a kind of read-only state for the Container.
495
- // Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
496
- if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
497
- throw new UsageError("The container is disposing or disposed");
498
- }
499
- while (this._context === undefined) {
500
- await new Promise((resolve, reject) => {
501
- const contextChangedHandler = () => {
502
- resolve();
503
- this.off("disposed", disposedHandler);
504
- };
505
- const disposedHandler = (error) => {
506
- reject(error !== null && error !== void 0 ? error : "The Container is disposed");
507
- this.off("contextChanged", contextChangedHandler);
508
- };
509
- this.once("contextChanged", contextChangedHandler);
510
- this.once("disposed", disposedHandler);
511
- });
512
- // The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
513
- // should have set this._context; making sure.
514
- assert(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
487
+ if (this._disposed) {
488
+ throw new UsageError("The context is already disposed");
489
+ }
490
+ if (this._runtime !== undefined) {
491
+ return (_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a);
515
492
  }
516
- // Disable lint rule for the sake of more complete stack traces
517
- // eslint-disable-next-line no-return-await
518
- return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
493
+ return new Promise((resolve, reject) => {
494
+ const runtimeInstantiatedHandler = () => {
495
+ var _a, _b;
496
+ assert(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
497
+ resolve((_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
498
+ this._lifecycleEvents.off("disposed", disposedHandler);
499
+ };
500
+ const disposedHandler = () => {
501
+ reject(new Error("ContainerContext was disposed"));
502
+ this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
503
+ };
504
+ this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
505
+ this._lifecycleEvents.once("disposed", disposedHandler);
506
+ });
519
507
  }
520
508
  /**
521
509
  * Retrieves the quorum associated with the document
@@ -524,7 +512,7 @@ export class Container extends EventEmitterWithErrorHandling {
524
512
  return this.protocolHandler.quorum;
525
513
  }
526
514
  dispose(error) {
527
- this._deltaManager.close(error, true /* doDispose */);
515
+ this._deltaManager.dispose(error);
528
516
  this.verifyClosed();
529
517
  }
530
518
  close(error) {
@@ -540,7 +528,7 @@ export class Container extends EventEmitterWithErrorHandling {
540
528
  assert(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
541
529
  }
542
530
  closeCore(error) {
543
- var _a, _b, _c;
531
+ var _a;
544
532
  assert(!this.closed, 0x315 /* re-entrancy */);
545
533
  try {
546
534
  // Ensure that we raise all key events even if one of these throws
@@ -558,12 +546,6 @@ export class Container extends EventEmitterWithErrorHandling {
558
546
  this._lifecycleState = "closing";
559
547
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
560
548
  this.connectionStateHandler.dispose();
561
- (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
562
- this.storageAdapter.dispose();
563
- // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
564
- // about file, like file being overwritten in storage, but client having stale local cache.
565
- // Driver need to ensure all caches are cleared on critical errors
566
- (_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
567
549
  }
568
550
  catch (exception) {
569
551
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
@@ -575,6 +557,10 @@ export class Container extends EventEmitterWithErrorHandling {
575
557
  }
576
558
  finally {
577
559
  this._lifecycleState = "closed";
560
+ // There is no user for summarizer, so we need to ensure dispose is called
561
+ if (this.client.details.type === summarizerClientType) {
562
+ this.dispose(error);
563
+ }
578
564
  }
579
565
  }
580
566
  disposeCore(error) {
@@ -588,7 +574,8 @@ export class Container extends EventEmitterWithErrorHandling {
588
574
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
589
575
  this.mc.logger.sendTelemetryEvent({
590
576
  eventName: "ContainerDispose",
591
- category: "generic",
577
+ // Only log error if container isn't closed
578
+ category: !this.closed && error !== undefined ? "error" : "generic",
592
579
  }, error);
593
580
  // ! Progressing from "closed" to "disposing" is not allowed
594
581
  if (this._lifecycleState !== "closed") {
@@ -596,7 +583,8 @@ export class Container extends EventEmitterWithErrorHandling {
596
583
  }
597
584
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
598
585
  this.connectionStateHandler.dispose();
599
- (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
586
+ const maybeError = error !== undefined ? new Error(error.message) : undefined;
587
+ (_b = this._runtime) === null || _b === void 0 ? void 0 : _b.dispose(maybeError);
600
588
  this.storageAdapter.dispose();
601
589
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
602
590
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -614,6 +602,7 @@ export class Container extends EventEmitterWithErrorHandling {
614
602
  }
615
603
  finally {
616
604
  this._lifecycleState = "disposed";
605
+ this._lifecycleEvents.emit("disposed");
617
606
  }
618
607
  }
619
608
  closeAndGetPendingLocalState() {
@@ -628,12 +617,15 @@ export class Container extends EventEmitterWithErrorHandling {
628
617
  if (!this.offlineLoadEnabled) {
629
618
  throw new UsageError("Can't get pending local state unless offline load is enabled");
630
619
  }
620
+ if (this.closed || this._disposed) {
621
+ throw new UsageError("Pending state cannot be retried if the container is closed or disposed");
622
+ }
631
623
  assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
632
624
  assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
633
625
  assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
634
626
  assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
635
627
  const pendingState = {
636
- pendingRuntimeState: this.context.getPendingLocalState(),
628
+ pendingRuntimeState: this.runtime.getPendingLocalState(),
637
629
  baseSnapshot: this.baseSnapshot,
638
630
  snapshotBlobs: this.baseSnapshotBlobs,
639
631
  savedOps: this.savedOps,
@@ -649,11 +641,10 @@ export class Container extends EventEmitterWithErrorHandling {
649
641
  }
650
642
  serialize() {
651
643
  assert(this.attachState === AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
652
- const appSummary = this.context.createSummary();
644
+ const appSummary = this.runtime.createSummary();
653
645
  const protocolSummary = this.captureProtocolSummary();
654
646
  const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
655
- if (this.loader.services.detachedBlobStorage &&
656
- this.loader.services.detachedBlobStorage.size > 0) {
647
+ if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
657
648
  combinedSummary.tree[".hasAttachmentBlobs"] = {
658
649
  type: SummaryType.Blob,
659
650
  content: "true",
@@ -672,15 +663,14 @@ export class Container extends EventEmitterWithErrorHandling {
672
663
  assert(this._attachState === AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
673
664
  this.attachStarted = true;
674
665
  // If attachment blobs were uploaded in detached state we will go through a different attach flow
675
- const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined &&
676
- this.loader.services.detachedBlobStorage.size > 0;
666
+ const hasAttachmentBlobs = this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
677
667
  try {
678
668
  assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
679
669
  let summary;
680
670
  if (!hasAttachmentBlobs) {
681
671
  // Get the document state post attach - possibly can just call attach but we need to change the
682
672
  // semantics around what the attach means as far as async code goes.
683
- const appSummary = this.context.createSummary();
673
+ const appSummary = this.runtime.createSummary();
684
674
  const protocolSummary = this.captureProtocolSummary();
685
675
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
686
676
  // Set the state as attaching as we are starting the process of attaching container.
@@ -688,6 +678,7 @@ export class Container extends EventEmitterWithErrorHandling {
688
678
  // starting to attach the container to storage.
689
679
  // Also, this should only be fired in detached container.
690
680
  this._attachState = AttachState.Attaching;
681
+ this.runtime.setAttachState(AttachState.Attaching);
691
682
  this.emit("attaching");
692
683
  if (this.offlineLoadEnabled) {
693
684
  const snapshot = getSnapshotTreeFromSerializedContainer(summary);
@@ -697,40 +688,38 @@ export class Container extends EventEmitterWithErrorHandling {
697
688
  }
698
689
  }
699
690
  // Actually go and create the resolved document
700
- const createNewResolvedUrl = await this.urlResolver.resolve(request);
701
- ensureFluidResolvedUrl(createNewResolvedUrl);
702
691
  if (this.service === undefined) {
703
- assert(this.client.details.type !== summarizerClientType, 0x2c4 /* "client should not be summarizer before container is created" */);
692
+ const createNewResolvedUrl = await this.urlResolver.resolve(request);
693
+ assert(this.client.details.type !== summarizerClientType &&
694
+ createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
704
695
  this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
705
- cancel: this.closeSignal,
696
+ cancel: this._deltaManager.closeAbortController.signal,
706
697
  });
707
698
  }
708
- const resolvedUrl = this.service.resolvedUrl;
709
- ensureFluidResolvedUrl(resolvedUrl);
710
- this._resolvedUrl = resolvedUrl;
711
699
  await this.storageAdapter.connectToService(this.service);
712
700
  if (hasAttachmentBlobs) {
713
701
  // upload blobs to storage
714
- assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
702
+ assert(!!this.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
715
703
  // build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
716
704
  // support blob handles that only know about the local IDs
717
705
  const redirectTable = new Map();
718
706
  // if new blobs are added while uploading, upload them too
719
- while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
720
- const newIds = this.loader.services.detachedBlobStorage
707
+ while (redirectTable.size < this.detachedBlobStorage.size) {
708
+ const newIds = this.detachedBlobStorage
721
709
  .getBlobIds()
722
710
  .filter((id) => !redirectTable.has(id));
723
711
  for (const id of newIds) {
724
- const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
712
+ const blob = await this.detachedBlobStorage.readBlob(id);
725
713
  const response = await this.storageAdapter.createBlob(blob);
726
714
  redirectTable.set(id, response.id);
727
715
  }
728
716
  }
729
717
  // take summary and upload
730
- const appSummary = this.context.createSummary(redirectTable);
718
+ const appSummary = this.runtime.createSummary(redirectTable);
731
719
  const protocolSummary = this.captureProtocolSummary();
732
720
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
733
721
  this._attachState = AttachState.Attaching;
722
+ this.runtime.setAttachState(AttachState.Attaching);
734
723
  this.emit("attaching");
735
724
  if (this.offlineLoadEnabled) {
736
725
  const snapshot = getSnapshotTreeFromSerializedContainer(summary);
@@ -745,6 +734,7 @@ export class Container extends EventEmitterWithErrorHandling {
745
734
  });
746
735
  }
747
736
  this._attachState = AttachState.Attached;
737
+ this.runtime.setAttachState(AttachState.Attached);
748
738
  this.emit("attached");
749
739
  if (!this.closed) {
750
740
  this.resumeInternal({
@@ -756,18 +746,14 @@ export class Container extends EventEmitterWithErrorHandling {
756
746
  catch (error) {
757
747
  // add resolved URL on error object so that host has the ability to find this document and delete it
758
748
  const newError = normalizeError(error);
759
- const resolvedUrl = this.resolvedUrl;
760
- if (isFluidResolvedUrl(resolvedUrl)) {
761
- newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
762
- }
749
+ newError.addTelemetryProperties({ resolvedUrl: (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.url });
763
750
  this.close(newError);
764
- (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
765
751
  throw newError;
766
752
  }
767
753
  }, { start: true, end: true, cancel: "generic" });
768
754
  }
769
755
  async request(path) {
770
- return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
756
+ return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
771
757
  }
772
758
  setAutoReconnectInternal(mode) {
773
759
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
@@ -833,13 +819,6 @@ export class Container extends EventEmitterWithErrorHandling {
833
819
  // Ensure connection to web socket
834
820
  this.connectToDeltaStream(args);
835
821
  }
836
- async getAbsoluteUrl(relativeUrl) {
837
- var _a;
838
- if (this.resolvedUrl === undefined) {
839
- return undefined;
840
- }
841
- return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName((_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails));
842
- }
843
822
  async proposeCodeDetails(codeDetails) {
844
823
  if (!isFluidCodeDetails(codeDetails)) {
845
824
  throw new Error("Provided codeDetails are not IFluidCodeDetails");
@@ -856,13 +835,12 @@ export class Container extends EventEmitterWithErrorHandling {
856
835
  .catch(() => false);
857
836
  }
858
837
  async processCodeProposal() {
859
- var _a;
860
838
  const codeDetails = this.getCodeDetailsFromQuorum();
861
839
  await Promise.all([
862
840
  this.deltaManager.inbound.pause(),
863
841
  this.deltaManager.inboundSignal.pause(),
864
842
  ]);
865
- if ((await this.context.satisfies(codeDetails)) === true) {
843
+ if ((await this.satisfies(codeDetails)) === true) {
866
844
  this.deltaManager.inbound.resume();
867
845
  this.deltaManager.inboundSignal.resume();
868
846
  return;
@@ -870,19 +848,44 @@ export class Container extends EventEmitterWithErrorHandling {
870
848
  // pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
871
849
  const error = new GenericError("Existing context does not satisfy incoming proposal");
872
850
  this.close(error);
873
- (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
851
+ }
852
+ /**
853
+ * Determines if the currently loaded module satisfies the incoming constraint code details
854
+ */
855
+ async satisfies(constraintCodeDetails) {
856
+ var _a, _b;
857
+ // If we have no module, it can't satisfy anything.
858
+ if (this._loadedModule === undefined) {
859
+ return false;
860
+ }
861
+ const comparers = [];
862
+ const maybeCompareCodeLoader = this.codeLoader;
863
+ if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
864
+ comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
865
+ }
866
+ const maybeCompareExport = (_a = this._loadedModule) === null || _a === void 0 ? void 0 : _a.module.fluidExport;
867
+ if ((maybeCompareExport === null || maybeCompareExport === void 0 ? void 0 : maybeCompareExport.IFluidCodeDetailsComparer) !== undefined) {
868
+ comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
869
+ }
870
+ // If there are no comparers, then it's impossible to know if the currently loaded package satisfies
871
+ // the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
872
+ // rather than potentially running with incompatible code.
873
+ if (comparers.length === 0) {
874
+ return false;
875
+ }
876
+ for (const comparer of comparers) {
877
+ const satisfies = await comparer.satisfies((_b = this._loadedModule) === null || _b === void 0 ? void 0 : _b.details, constraintCodeDetails);
878
+ if (satisfies === false) {
879
+ return false;
880
+ }
881
+ }
882
+ return true;
874
883
  }
875
884
  async getVersion(version) {
876
885
  const versions = await this.storageAdapter.getVersions(version, 1);
877
886
  return versions[0];
878
887
  }
879
- recordConnectStartTime() {
880
- if (this.connectionTransitionTimes[ConnectionState.Disconnected] === undefined) {
881
- this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
882
- }
883
- }
884
888
  connectToDeltaStream(args) {
885
- this.recordConnectStartTime();
886
889
  // All agents need "write" access, including summarizer.
887
890
  if (!this._canReconnect || !this.client.details.capabilities.interactive) {
888
891
  args.mode = "write";
@@ -894,12 +897,9 @@ export class Container extends EventEmitterWithErrorHandling {
894
897
  *
895
898
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
896
899
  */
897
- async load(specifiedVersion, loadMode, pendingLocalState) {
898
- var _a;
899
- if (this._resolvedUrl === undefined) {
900
- throw new Error("Attempting to load without a resolved url");
901
- }
902
- this.service = await this.serviceFactory.createDocumentService(this._resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
900
+ async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
901
+ var _a, _b, _c;
902
+ this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
903
903
  // Ideally we always connect as "read" by default.
904
904
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
905
905
  // We should not rely on it by (one of them will address the issue, but we need to address both)
@@ -925,9 +925,7 @@ export class Container extends EventEmitterWithErrorHandling {
925
925
  else {
926
926
  // if we have pendingLocalState we can load without storage; don't wait for connection
927
927
  this.storageAdapter.connectToService(this.service).catch((error) => {
928
- var _a;
929
928
  this.close(error);
930
- (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
931
929
  });
932
930
  }
933
931
  this._attachState = AttachState.Attached;
@@ -944,7 +942,7 @@ export class Container extends EventEmitterWithErrorHandling {
944
942
  if (this.offlineLoadEnabled) {
945
943
  this.baseSnapshot = snapshot;
946
944
  // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
947
- this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storage);
945
+ this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storageAdapter);
948
946
  }
949
947
  }
950
948
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
@@ -980,7 +978,7 @@ export class Container extends EventEmitterWithErrorHandling {
980
978
  for (const message of pendingLocalState.savedOps) {
981
979
  this.processRemoteMessage(message);
982
980
  // allow runtime to apply stashed ops at this op's sequence number
983
- await this.context.notifyOpReplay(message);
981
+ await ((_c = (_b = this.runtime).notifyOpReplay) === null || _c === void 0 ? void 0 : _c.call(_b, message));
984
982
  }
985
983
  pendingLocalState.savedOps = [];
986
984
  // now set clientId to stashed clientId so live ops are correctly processed as local
@@ -1053,8 +1051,7 @@ export class Container extends EventEmitterWithErrorHandling {
1053
1051
  }
1054
1052
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
1055
1053
  if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
1056
- assert(!!this.loader.services.detachedBlobStorage &&
1057
- this.loader.services.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
1054
+ assert(!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
1058
1055
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
1059
1056
  }
1060
1057
  const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
@@ -1107,9 +1104,7 @@ export class Container extends EventEmitterWithErrorHandling {
1107
1104
  this.initializeProtocolState(attributes, quorumSnapshot);
1108
1105
  }
1109
1106
  initializeProtocolState(attributes, quorumSnapshot) {
1110
- var _a;
1111
- const protocolHandlerBuilder = (_a = this.protocolHandlerBuilder) !== null && _a !== void 0 ? _a : ((...args) => new ProtocolHandler(...args, new Audience()));
1112
- const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })));
1107
+ const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })));
1113
1108
  const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
1114
1109
  protocol.quorum.on("error", (error) => {
1115
1110
  protocolLogger.sendErrorEvent(error);
@@ -1129,10 +1124,8 @@ export class Container extends EventEmitterWithErrorHandling {
1129
1124
  });
1130
1125
  }
1131
1126
  this.processCodeProposal().catch((error) => {
1132
- var _a;
1133
1127
  const normalizedError = normalizeError(error);
1134
1128
  this.close(normalizedError);
1135
- (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, normalizedError);
1136
1129
  throw error;
1137
1130
  });
1138
1131
  }
@@ -1216,9 +1209,15 @@ export class Container extends EventEmitterWithErrorHandling {
1216
1209
  assert(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
1217
1210
  this.connectionStateHandler.receivedConnectEvent(details);
1218
1211
  });
1212
+ deltaManager.on("establishingConnection", (reason) => {
1213
+ this.connectionStateHandler.establishingConnection(reason);
1214
+ });
1215
+ deltaManager.on("cancelEstablishingConnection", (reason) => {
1216
+ this.connectionStateHandler.cancelEstablishingConnection(reason);
1217
+ });
1219
1218
  deltaManager.on("disconnect", (reason, error) => {
1220
1219
  var _a;
1221
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1220
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
1222
1221
  if (!this.closed) {
1223
1222
  this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1224
1223
  }
@@ -1272,10 +1271,12 @@ export class Container extends EventEmitterWithErrorHandling {
1272
1271
  time - this.connectionTransitionTimes[ConnectionState.Disconnected];
1273
1272
  durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
1274
1273
  }
1275
- else {
1276
- // This info is of most interest on establishing connection only.
1274
+ else if (value === ConnectionState.CatchingUp) {
1275
+ // This info is of most interesting while Catching Up.
1277
1276
  checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
1278
- if (this.deltaManager.hasCheckpointSequenceNumber) {
1277
+ // Need to check that we have already loaded and fetched the snapshot.
1278
+ if (this.deltaManager.hasCheckpointSequenceNumber &&
1279
+ this._lifecycleState === "loaded") {
1279
1280
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1280
1281
  }
1281
1282
  }
@@ -1322,7 +1323,6 @@ export class Container extends EventEmitterWithErrorHandling {
1322
1323
  }
1323
1324
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1324
1325
  submitContainerMessage(type, contents, batch, metadata) {
1325
- var _a;
1326
1326
  switch (type) {
1327
1327
  case MessageType.Operation:
1328
1328
  return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
@@ -1331,7 +1331,6 @@ export class Container extends EventEmitterWithErrorHandling {
1331
1331
  default: {
1332
1332
  const newError = new GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type });
1333
1333
  this.close(newError);
1334
- (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
1335
1334
  return -1;
1336
1335
  }
1337
1336
  }
@@ -1364,7 +1363,7 @@ export class Container extends EventEmitterWithErrorHandling {
1364
1363
  return -1;
1365
1364
  }
1366
1365
  this.messageCountAfterDisconnection += 1;
1367
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1366
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
1368
1367
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1369
1368
  }
1370
1369
  processRemoteMessage(message) {
@@ -1372,24 +1371,51 @@ export class Container extends EventEmitterWithErrorHandling {
1372
1371
  this.savedOps.push(message);
1373
1372
  }
1374
1373
  const local = this.clientId === message.clientId;
1374
+ // Check and report if we're getting messages from a clientId that we previously
1375
+ // flagged should have left, or from a client that's not in the quorum but should be
1376
+ if (message.clientId != null) {
1377
+ const client = this.protocolHandler.quorum.getMember(message.clientId);
1378
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
1379
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
1380
+ throw new Error("Remote message's clientId is missing from the quorum");
1381
+ }
1382
+ // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
1383
+ // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
1384
+ // document we don't need to blow up aggressively.
1385
+ if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
1386
+ !canBeCoalescedByService(message)) {
1387
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
1388
+ throw new Error("Remote message's clientId already should have left");
1389
+ }
1390
+ }
1375
1391
  // Allow the protocol handler to process the message
1376
1392
  const result = this.protocolHandler.processMessage(message, local);
1377
1393
  // Forward messages to the loaded runtime for processing
1378
- this.context.process(message, local);
1394
+ this.runtime.process(message, local);
1379
1395
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1380
1396
  if (this.activeConnection()) {
1381
- if (this.collabWindowTracker === undefined) {
1397
+ if (this.noopHeuristic === undefined) {
1398
+ const serviceConfiguration = this.deltaManager.serviceConfiguration;
1382
1399
  // Note that config from first connection will be used for this container's lifetime.
1383
1400
  // That means that if relay service changes settings, such changes will impact only newly booted
1384
1401
  // clients.
1385
1402
  // All existing will continue to use settings they got earlier.
1386
- assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1387
- this.collabWindowTracker = new CollabWindowTracker((type) => {
1388
- assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1389
- this.submitMessage(type);
1390
- }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1403
+ assert(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1404
+ this.noopHeuristic = new NoopHeuristic(serviceConfiguration.noopTimeFrequency, serviceConfiguration.noopCountFrequency);
1405
+ this.noopHeuristic.on("wantsNoop", () => {
1406
+ // On disconnect we notify the heuristic which should prevent it from wanting a noop.
1407
+ // Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
1408
+ // running the microtask that the heuristic queued in response.
1409
+ assert(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
1410
+ this.submitMessage(MessageType.NoOp);
1411
+ });
1412
+ }
1413
+ this.noopHeuristic.notifyMessageProcessed(message);
1414
+ // The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
1415
+ if (result.immediateNoOp === true) {
1416
+ // ADO:1385: Remove cast and use MessageType once definition changes propagate
1417
+ this.submitMessage(MessageType2.Accept);
1391
1418
  }
1392
- this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1393
1419
  }
1394
1420
  this.emit("op", message);
1395
1421
  }
@@ -1398,12 +1424,12 @@ export class Container extends EventEmitterWithErrorHandling {
1398
1424
  }
1399
1425
  processSignal(message) {
1400
1426
  // No clientId indicates a system signal message.
1401
- if (message.clientId === null) {
1427
+ if (protocolHandlerShouldProcessSignal(message)) {
1402
1428
  this.protocolHandler.processSignal(message);
1403
1429
  }
1404
1430
  else {
1405
1431
  const local = this.clientId === message.clientId;
1406
- this.context.processSignal(message, local);
1432
+ this.runtime.processSignal(message, local);
1407
1433
  }
1408
1434
  }
1409
1435
  /**
@@ -1436,20 +1462,37 @@ export class Container extends EventEmitterWithErrorHandling {
1436
1462
  await this.instantiateContext(existing, codeDetails, snapshot);
1437
1463
  }
1438
1464
  async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1439
- var _a;
1440
- assert(((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing context not disposed" */);
1465
+ var _a, _b;
1466
+ assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
1441
1467
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1442
1468
  // are set. Global requests will still go directly to the loader
1443
- const loader = new RelativeLoader(this, this.loader);
1444
- this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => { var _a; return (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error); }, (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1445
- this.emit("contextChanged", codeDetails);
1446
- }
1447
- updateDirtyContainerState(dirty) {
1448
- if (this._dirtyContainer === dirty) {
1449
- return;
1469
+ const maybeLoader = this.scope;
1470
+ const loader = new RelativeLoader(this, maybeLoader.ILoader);
1471
+ const loadCodeResult = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "CodeLoad" }, async () => this.codeLoader.load(codeDetails));
1472
+ this._loadedModule = {
1473
+ module: loadCodeResult.module,
1474
+ // An older interface ICodeLoader could return an IFluidModule which didn't have details.
1475
+ // If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
1476
+ // TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
1477
+ details: (_b = loadCodeResult.details) !== null && _b !== void 0 ? _b : codeDetails,
1478
+ };
1479
+ const fluidExport = this._loadedModule.module.fluidExport;
1480
+ const runtimeFactory = fluidExport === null || fluidExport === void 0 ? void 0 : fluidExport.IRuntimeFactory;
1481
+ if (runtimeFactory === undefined) {
1482
+ throw new Error(packageNotFactoryError);
1450
1483
  }
1451
- this._dirtyContainer = dirty;
1452
- this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
1484
+ const getSpecifiedCodeDetails = () => {
1485
+ var _a;
1486
+ return ((_a = this.protocolHandler.quorum.get("code")) !== null && _a !== void 0 ? _a : this.protocolHandler.quorum.get("code2"));
1487
+ };
1488
+ const context = new ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, this.protocolHandler.audience, loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; }, () => this.clientId, () => this._deltaManager.serviceConfiguration, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
1489
+ this._lifecycleEvents.once("disposed", () => {
1490
+ context.dispose();
1491
+ });
1492
+ this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1493
+ this._lifecycleEvents.emit("runtimeInstantiated");
1494
+ this._loadedCodeDetails = codeDetails;
1495
+ this.emit("contextChanged", codeDetails);
1453
1496
  }
1454
1497
  /**
1455
1498
  * Set the connected state of the ContainerContext
@@ -1459,16 +1502,15 @@ export class Container extends EventEmitterWithErrorHandling {
1459
1502
  */
1460
1503
  setContextConnectedState(state, readonly) {
1461
1504
  var _a;
1462
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1505
+ if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1463
1506
  /**
1464
1507
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1465
1508
  * ops getting through to the DeltaManager.
1466
1509
  * The ContainerRuntime's "connected" state simply means it is ok to send ops
1467
1510
  * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1468
1511
  */
1469
- this.context.setConnectionState(state && !readonly, this.clientId);
1512
+ this.runtime.setConnectionState(state && !readonly, this.clientId);
1470
1513
  }
1471
1514
  }
1472
1515
  }
1473
- Container.version = "^0.1.0";
1474
1516
  //# sourceMappingURL=container.js.map