@fluidframework/container-loader 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229

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 (168) hide show
  1. package/.eslintrc.js +18 -21
  2. package/.mocharc.js +2 -2
  3. package/README.md +65 -44
  4. package/api-extractor.json +2 -2
  5. package/closeAndGetPendingLocalState.md +51 -0
  6. package/dist/audience.d.ts +0 -1
  7. package/dist/audience.d.ts.map +1 -1
  8. package/dist/audience.js.map +1 -1
  9. package/dist/catchUpMonitor.d.ts.map +1 -1
  10. package/dist/catchUpMonitor.js.map +1 -1
  11. package/dist/collabWindowTracker.d.ts.map +1 -1
  12. package/dist/collabWindowTracker.js.map +1 -1
  13. package/dist/connectionManager.d.ts +5 -5
  14. package/dist/connectionManager.d.ts.map +1 -1
  15. package/dist/connectionManager.js +107 -44
  16. package/dist/connectionManager.js.map +1 -1
  17. package/dist/connectionState.d.ts.map +1 -1
  18. package/dist/connectionState.js.map +1 -1
  19. package/dist/connectionStateHandler.d.ts +7 -7
  20. package/dist/connectionStateHandler.d.ts.map +1 -1
  21. package/dist/connectionStateHandler.js +50 -21
  22. package/dist/connectionStateHandler.js.map +1 -1
  23. package/dist/container.d.ts +64 -5
  24. package/dist/container.d.ts.map +1 -1
  25. package/dist/container.js +329 -137
  26. package/dist/container.js.map +1 -1
  27. package/dist/containerContext.d.ts +19 -8
  28. package/dist/containerContext.d.ts.map +1 -1
  29. package/dist/containerContext.js +58 -14
  30. package/dist/containerContext.js.map +1 -1
  31. package/dist/containerStorageAdapter.d.ts +41 -2
  32. package/dist/containerStorageAdapter.d.ts.map +1 -1
  33. package/dist/containerStorageAdapter.js +88 -14
  34. package/dist/containerStorageAdapter.js.map +1 -1
  35. package/dist/contracts.d.ts +3 -3
  36. package/dist/contracts.d.ts.map +1 -1
  37. package/dist/contracts.js.map +1 -1
  38. package/dist/deltaManager.d.ts +21 -8
  39. package/dist/deltaManager.d.ts.map +1 -1
  40. package/dist/deltaManager.js +112 -37
  41. package/dist/deltaManager.js.map +1 -1
  42. package/dist/deltaManagerProxy.d.ts +10 -22
  43. package/dist/deltaManagerProxy.d.ts.map +1 -1
  44. package/dist/deltaManagerProxy.js +14 -50
  45. package/dist/deltaManagerProxy.js.map +1 -1
  46. package/dist/deltaQueue.d.ts.map +1 -1
  47. package/dist/deltaQueue.js +4 -2
  48. package/dist/deltaQueue.js.map +1 -1
  49. package/dist/index.d.ts +4 -3
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1 -3
  52. package/dist/index.js.map +1 -1
  53. package/dist/loader.d.ts +13 -4
  54. package/dist/loader.d.ts.map +1 -1
  55. package/dist/loader.js +38 -24
  56. package/dist/loader.js.map +1 -1
  57. package/dist/packageVersion.d.ts +1 -1
  58. package/dist/packageVersion.js +1 -1
  59. package/dist/packageVersion.js.map +1 -1
  60. package/dist/protocol.d.ts.map +1 -1
  61. package/dist/protocol.js +2 -1
  62. package/dist/protocol.js.map +1 -1
  63. package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
  64. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  65. package/dist/protocolTreeDocumentStorageService.js +7 -4
  66. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  67. package/dist/quorum.d.ts.map +1 -1
  68. package/dist/quorum.js.map +1 -1
  69. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  70. package/dist/retriableDocumentStorageService.js +6 -2
  71. package/dist/retriableDocumentStorageService.js.map +1 -1
  72. package/dist/utils.d.ts.map +1 -1
  73. package/dist/utils.js +8 -5
  74. package/dist/utils.js.map +1 -1
  75. package/lib/audience.d.ts +0 -1
  76. package/lib/audience.d.ts.map +1 -1
  77. package/lib/audience.js.map +1 -1
  78. package/lib/catchUpMonitor.d.ts.map +1 -1
  79. package/lib/catchUpMonitor.js.map +1 -1
  80. package/lib/collabWindowTracker.d.ts.map +1 -1
  81. package/lib/collabWindowTracker.js.map +1 -1
  82. package/lib/connectionManager.d.ts +5 -5
  83. package/lib/connectionManager.d.ts.map +1 -1
  84. package/lib/connectionManager.js +110 -47
  85. package/lib/connectionManager.js.map +1 -1
  86. package/lib/connectionState.d.ts.map +1 -1
  87. package/lib/connectionState.js.map +1 -1
  88. package/lib/connectionStateHandler.d.ts +7 -7
  89. package/lib/connectionStateHandler.d.ts.map +1 -1
  90. package/lib/connectionStateHandler.js +50 -21
  91. package/lib/connectionStateHandler.js.map +1 -1
  92. package/lib/container.d.ts +64 -5
  93. package/lib/container.d.ts.map +1 -1
  94. package/lib/container.js +336 -144
  95. package/lib/container.js.map +1 -1
  96. package/lib/containerContext.d.ts +19 -8
  97. package/lib/containerContext.d.ts.map +1 -1
  98. package/lib/containerContext.js +59 -15
  99. package/lib/containerContext.js.map +1 -1
  100. package/lib/containerStorageAdapter.d.ts +41 -2
  101. package/lib/containerStorageAdapter.d.ts.map +1 -1
  102. package/lib/containerStorageAdapter.js +86 -14
  103. package/lib/containerStorageAdapter.js.map +1 -1
  104. package/lib/contracts.d.ts +3 -3
  105. package/lib/contracts.d.ts.map +1 -1
  106. package/lib/contracts.js.map +1 -1
  107. package/lib/deltaManager.d.ts +21 -8
  108. package/lib/deltaManager.d.ts.map +1 -1
  109. package/lib/deltaManager.js +114 -39
  110. package/lib/deltaManager.js.map +1 -1
  111. package/lib/deltaManagerProxy.d.ts +10 -22
  112. package/lib/deltaManagerProxy.d.ts.map +1 -1
  113. package/lib/deltaManagerProxy.js +14 -50
  114. package/lib/deltaManagerProxy.js.map +1 -1
  115. package/lib/deltaQueue.d.ts.map +1 -1
  116. package/lib/deltaQueue.js +4 -2
  117. package/lib/deltaQueue.js.map +1 -1
  118. package/lib/index.d.ts +4 -3
  119. package/lib/index.d.ts.map +1 -1
  120. package/lib/index.js +2 -2
  121. package/lib/index.js.map +1 -1
  122. package/lib/loader.d.ts +13 -4
  123. package/lib/loader.d.ts.map +1 -1
  124. package/lib/loader.js +37 -24
  125. package/lib/loader.js.map +1 -1
  126. package/lib/packageVersion.d.ts +1 -1
  127. package/lib/packageVersion.js +1 -1
  128. package/lib/packageVersion.js.map +1 -1
  129. package/lib/protocol.d.ts.map +1 -1
  130. package/lib/protocol.js +2 -1
  131. package/lib/protocol.js.map +1 -1
  132. package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
  133. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  134. package/lib/protocolTreeDocumentStorageService.js +7 -4
  135. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  136. package/lib/quorum.d.ts.map +1 -1
  137. package/lib/quorum.js.map +1 -1
  138. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  139. package/lib/retriableDocumentStorageService.js +6 -2
  140. package/lib/retriableDocumentStorageService.js.map +1 -1
  141. package/lib/utils.d.ts.map +1 -1
  142. package/lib/utils.js +8 -5
  143. package/lib/utils.js.map +1 -1
  144. package/package.json +67 -56
  145. package/prettier.config.cjs +1 -1
  146. package/src/audience.ts +51 -46
  147. package/src/catchUpMonitor.ts +39 -37
  148. package/src/collabWindowTracker.ts +75 -70
  149. package/src/connectionManager.ts +1040 -941
  150. package/src/connectionState.ts +19 -19
  151. package/src/connectionStateHandler.ts +557 -463
  152. package/src/container.ts +2147 -1784
  153. package/src/containerContext.ts +417 -345
  154. package/src/containerStorageAdapter.ts +268 -154
  155. package/src/contracts.ts +155 -153
  156. package/src/deltaManager.ts +1074 -945
  157. package/src/deltaManagerProxy.ts +88 -137
  158. package/src/deltaQueue.ts +155 -151
  159. package/src/index.ts +13 -17
  160. package/src/loader.ts +434 -427
  161. package/src/packageVersion.ts +1 -1
  162. package/src/protocol.ts +93 -87
  163. package/src/protocolTreeDocumentStorageService.ts +34 -34
  164. package/src/quorum.ts +34 -34
  165. package/src/retriableDocumentStorageService.ts +118 -102
  166. package/src/utils.ts +93 -83
  167. package/tsconfig.esnext.json +6 -6
  168. package/tsconfig.json +8 -12
@@ -4,360 +4,432 @@
4
4
  */
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
- import { LazyPromise } from "@fluidframework/common-utils";
7
+ import { assert, LazyPromise, TypedEventEmitter } from "@fluidframework/common-utils";
8
8
  import {
9
- IAudience,
10
- IContainerContext,
11
- IDeltaManager,
12
- ILoader,
13
- IRuntime,
14
- ICriticalContainerError,
15
- AttachState,
16
- ILoaderOptions,
17
- IRuntimeFactory,
18
- IProvideRuntimeFactory,
19
- IFluidCodeDetails,
20
- IFluidCodeDetailsComparer,
21
- IProvideFluidCodeDetailsComparer,
22
- ICodeDetailsLoader,
23
- IFluidModuleWithDetails,
24
- ISnapshotTreeWithBlobContents,
25
- IBatchMessage,
9
+ IAudience,
10
+ IContainerContext,
11
+ IDeltaManager,
12
+ ILoader,
13
+ IRuntime,
14
+ ICriticalContainerError,
15
+ AttachState,
16
+ ILoaderOptions,
17
+ IRuntimeFactory,
18
+ IProvideRuntimeFactory,
19
+ IFluidCodeDetails,
20
+ IFluidCodeDetailsComparer,
21
+ IProvideFluidCodeDetailsComparer,
22
+ ICodeDetailsLoader,
23
+ IFluidModuleWithDetails,
24
+ IBatchMessage,
26
25
  } from "@fluidframework/container-definitions";
27
- import {
28
- IRequest,
29
- IResponse,
30
- FluidObject,
31
- } from "@fluidframework/core-interfaces";
26
+ import { IRequest, IResponse, FluidObject } from "@fluidframework/core-interfaces";
32
27
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
33
28
  import { isFluidResolvedUrl } from "@fluidframework/driver-utils";
34
29
  import {
35
- IClientConfiguration,
36
- IClientDetails,
37
- IDocumentMessage,
38
- IQuorum,
39
- IQuorumClients,
40
- ISequencedDocumentMessage,
41
- ISignalMessage,
42
- ISnapshotTree,
43
- ISummaryTree,
44
- IVersion,
45
- MessageType,
46
- ISummaryContent,
30
+ IClientConfiguration,
31
+ IClientDetails,
32
+ IDocumentMessage,
33
+ IQuorum,
34
+ IQuorumClients,
35
+ ISequencedDocumentMessage,
36
+ ISignalMessage,
37
+ ISnapshotTree,
38
+ ISummaryTree,
39
+ IVersion,
40
+ MessageType,
41
+ ISummaryContent,
47
42
  } from "@fluidframework/protocol-definitions";
48
43
  import { PerformanceEvent } from "@fluidframework/telemetry-utils";
49
- import { Container, ReportIfTooLong } from "./container";
44
+ import { UsageError } from "@fluidframework/container-utils";
45
+ import { Container } from "./container";
50
46
 
51
47
  const PackageNotFactoryError = "Code package does not implement IRuntimeFactory";
52
48
 
49
+ /**
50
+ * Events that {@link ContainerContext} can emit through its lifecycle.
51
+ *
52
+ * "runtimeInstantiated" - When an {@link @fluidframework/container-definitions#IRuntime} has been instantiated (by
53
+ * calling instantiateRuntime() on the runtime factory), and this._runtime is set.
54
+ *
55
+ * "disposed" - When its dispose() method is called. The {@link ContainerContext} is no longer usable at that point.
56
+ */
57
+ type ContextLifecycleEvents = "runtimeInstantiated" | "disposed";
58
+
53
59
  export class ContainerContext implements IContainerContext {
54
- public static async createOrLoad(
55
- container: Container,
56
- scope: FluidObject,
57
- codeLoader: ICodeDetailsLoader,
58
- codeDetails: IFluidCodeDetails,
59
- baseSnapshot: ISnapshotTree | undefined,
60
- deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
61
- quorum: IQuorum,
62
- loader: ILoader,
63
- submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
64
- submitSummaryFn: (summaryOp: ISummaryContent) => number,
65
- submitBatchFn: (batch: IBatchMessage[]) => number,
66
- submitSignalFn: (contents: any) => void,
67
- closeFn: (error?: ICriticalContainerError) => void,
68
- version: string,
69
- updateDirtyContainerState: (dirty: boolean) => void,
70
- existing: boolean,
71
- pendingLocalState?: unknown,
72
- ): Promise<ContainerContext> {
73
- const context = new ContainerContext(
74
- container,
75
- scope,
76
- codeLoader,
77
- codeDetails,
78
- baseSnapshot,
79
- deltaManager,
80
- quorum,
81
- loader,
82
- submitFn,
83
- submitSummaryFn,
84
- submitBatchFn,
85
- submitSignalFn,
86
- closeFn,
87
- version,
88
- updateDirtyContainerState,
89
- existing,
90
- pendingLocalState);
91
- await context.instantiateRuntime(existing);
92
- return context;
93
- }
94
-
95
- public readonly taggedLogger: ITelemetryLogger;
96
-
97
- public get clientId(): string | undefined {
98
- return this.container.clientId;
99
- }
100
-
101
- /**
102
- * DISCLAIMER: this id is only for telemetry purposes. Not suitable for any other usages.
103
- */
104
- public get id(): string {
105
- const resolvedUrl = this.container.resolvedUrl;
106
- if (isFluidResolvedUrl(resolvedUrl)) {
107
- return resolvedUrl.id;
108
- }
109
- return "";
110
- }
111
-
112
- public get clientDetails(): IClientDetails {
113
- return this.container.clientDetails;
114
- }
115
-
116
- private _connected: boolean;
117
- /**
118
- * When true, ops are free to flow
119
- * When false, ops should be kept as pending or rejected
120
- */
121
- public get connected(): boolean {
122
- return this._connected;
123
- }
124
-
125
- public get canSummarize(): boolean {
126
- return "summarize" in this.runtime;
127
- }
128
-
129
- public get serviceConfiguration(): IClientConfiguration | undefined {
130
- return this.container.serviceConfiguration;
131
- }
132
-
133
- public get audience(): IAudience {
134
- return this.container.audience;
135
- }
136
-
137
- public get options(): ILoaderOptions {
138
- return this.container.options;
139
- }
140
-
141
- public get baseSnapshot() {
142
- return this._baseSnapshot;
143
- }
144
-
145
- public get storage(): IDocumentStorageService {
146
- return this.container.storage;
147
- }
148
-
149
- private _runtime: IRuntime | undefined;
150
- private get runtime() {
151
- if (this._runtime === undefined) {
152
- throw new Error("Attempted to access runtime before it was defined");
153
- }
154
- return this._runtime;
155
- }
156
-
157
- private _disposed = false;
158
-
159
- public get disposed() {
160
- return this._disposed;
161
- }
162
-
163
- public get codeDetails() { return this._codeDetails; }
164
-
165
- private readonly _quorum: IQuorum;
166
- public get quorum(): IQuorumClients { return this._quorum; }
167
-
168
- private readonly _fluidModuleP: Promise<IFluidModuleWithDetails>;
169
-
170
- constructor(
171
- private readonly container: Container,
172
- public readonly scope: FluidObject,
173
- private readonly codeLoader: ICodeDetailsLoader,
174
- private readonly _codeDetails: IFluidCodeDetails,
175
- private _baseSnapshot: ISnapshotTree | undefined,
176
- public readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
177
- quorum: IQuorum,
178
- public readonly loader: ILoader,
179
- public readonly submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
180
- public readonly submitSummaryFn: (summaryOp: ISummaryContent) => number,
181
- /** @returns clientSequenceNumber of last message in a batch */
182
- public readonly submitBatchFn: (batch: IBatchMessage[]) => number,
183
- public readonly submitSignalFn: (contents: any) => void,
184
- public readonly closeFn: (error?: ICriticalContainerError) => void,
185
- public readonly version: string,
186
- public readonly updateDirtyContainerState: (dirty: boolean) => void,
187
- public readonly existing: boolean,
188
- public readonly pendingLocalState?: unknown,
189
-
190
- ) {
191
- this._connected = this.container.connected;
192
- this._quorum = quorum;
193
- this.taggedLogger = container.subLogger;
194
- this._fluidModuleP = new LazyPromise<IFluidModuleWithDetails>(
195
- async () => this.loadCodeModule(_codeDetails),
196
- );
197
- this.attachListener();
198
- }
199
-
200
- /**
201
- * @deprecated Temporary migratory API, to be removed when customers no longer need it.
202
- * When removed, `ContainerContext` should only take an {@link @fluidframework/container-definitions#IQuorumClients}
203
- * rather than an {@link @fluidframework/protocol-definitions#IQuorum}.
204
- * See {@link @fluidframework/container-definitions#IContainerContext} for more details.
205
- */
206
- public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
207
- return (this._quorum.get("code") ?? this._quorum.get("code2")) as IFluidCodeDetails | undefined;
208
- }
209
-
210
- public dispose(error?: Error): void {
211
- if (this._disposed) {
212
- return;
213
- }
214
- this._disposed = true;
215
-
216
- this.runtime.dispose(error);
217
- this._quorum.dispose();
218
- this.deltaManager.dispose();
219
- }
220
-
221
- public getLoadedFromVersion(): IVersion | undefined {
222
- return this.container.loadedFromVersion;
223
- }
224
-
225
- public get attachState(): AttachState {
226
- return this.container.attachState;
227
- }
228
-
229
- /**
230
- * Create a summary. Used when attaching or serializing a detached container.
231
- *
232
- * @param blobRedirectTable - A table passed during the attach process. While detached, blob upload is supported
233
- * using IDs generated locally. After attach, these IDs cannot be used, so this table maps the old local IDs to the
234
- * new storage IDs so requests can be redirected.
235
- */
236
- public createSummary(blobRedirectTable?: Map<string, string>): ISummaryTree {
237
- return this.runtime.createSummary(blobRedirectTable);
238
- }
239
-
240
- public setConnectionState(connected: boolean, clientId?: string) {
241
- const runtime = this.runtime;
242
- this._connected = connected;
243
- runtime.setConnectionState(connected, clientId);
244
- }
245
-
246
- public process(message: ISequencedDocumentMessage, local: boolean) {
247
- this.runtime.process(message, local);
248
- }
249
-
250
- public processSignal(message: ISignalMessage, local: boolean) {
251
- this.runtime.processSignal(message, local);
252
- }
253
-
254
- public async request(path: IRequest): Promise<IResponse> {
255
- return this.runtime.request(path);
256
- }
257
-
258
- public async getAbsoluteUrl(relativeUrl: string): Promise<string | undefined> {
259
- return this.container.getAbsoluteUrl(relativeUrl);
260
- }
261
-
262
- public getPendingLocalState(): unknown {
263
- return this.runtime.getPendingLocalState();
264
- }
265
-
266
- /**
267
- * Determines if the current code details of the context
268
- * satisfy the incoming constraint code details
269
- */
270
- public async satisfies(constraintCodeDetails: IFluidCodeDetails) {
271
- const comparers: IFluidCodeDetailsComparer[] = [];
272
-
273
- const maybeCompareCodeLoader = this.codeLoader;
274
- if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
275
- comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
276
- }
277
-
278
- const moduleWithDetails = await this._fluidModuleP;
279
- const maybeCompareExport: Partial<IProvideFluidCodeDetailsComparer> | undefined =
280
- moduleWithDetails.module?.fluidExport;
281
- if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
282
- comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
283
- }
284
-
285
- // if there are not comparers it is not possible to know
286
- // if the current satisfy the incoming, so return false,
287
- // as assuming they do not satisfy is safer .e.g we will
288
- // reload, rather than potentially running with
289
- // incompatible code
290
- if (comparers.length === 0) {
291
- return false;
292
- }
293
-
294
- for (const comparer of comparers) {
295
- const satisfies = await comparer.satisfies(
296
- moduleWithDetails.details,
297
- constraintCodeDetails,
298
- );
299
- if (satisfies === false) {
300
- return false;
301
- }
302
- }
303
- return true;
304
- }
305
-
306
- public notifyAttaching(snapshot: ISnapshotTreeWithBlobContents) {
307
- this._baseSnapshot = snapshot;
308
- this.runtime.notifyAttaching?.(snapshot);
309
- this.runtime.setAttachState(AttachState.Attaching);
310
- }
311
-
312
- // #region private
313
-
314
- private async getRuntimeFactory(): Promise<IRuntimeFactory> {
315
- const fluidExport: FluidObject<IProvideRuntimeFactory> | undefined =
316
- (await this._fluidModuleP).module?.fluidExport;
317
- const runtimeFactory = fluidExport?.IRuntimeFactory;
318
- if (runtimeFactory === undefined) {
319
- throw new Error(PackageNotFactoryError);
320
- }
321
-
322
- return runtimeFactory;
323
- }
324
-
325
- private async instantiateRuntime(existing: boolean) {
326
- const runtimeFactory = await this.getRuntimeFactory();
327
- await ReportIfTooLong(
328
- this.taggedLogger,
329
- "instantiateRuntime",
330
- async () => {
331
- this._runtime = await runtimeFactory.instantiateRuntime(this, existing);
332
- return {};
333
- });
334
- }
335
-
336
- private attachListener() {
337
- this.container.once("attached", () => {
338
- this.runtime.setAttachState(AttachState.Attached);
339
- });
340
- }
341
-
342
- private async loadCodeModule(codeDetails: IFluidCodeDetails): Promise<IFluidModuleWithDetails> {
343
- const loadCodeResult = await PerformanceEvent.timedExecAsync(
344
- this.taggedLogger,
345
- { eventName: "CodeLoad" },
346
- async () => this.codeLoader.load(codeDetails),
347
- );
348
-
349
- if ("module" in loadCodeResult) {
350
- const { module, details } = loadCodeResult;
351
- return {
352
- module,
353
- details: details ?? codeDetails,
354
- };
355
- } else {
356
- // If "module" is not in the result, we are using a legacy ICodeLoader. Fix the result up with details.
357
- // Once usage drops to 0 we can remove this compat path.
358
- this.taggedLogger.sendTelemetryEvent({ eventName: "LegacyCodeLoader" });
359
- return loadCodeResult;
360
- }
361
- }
362
- // #endregion
60
+ public static async createOrLoad(
61
+ container: Container,
62
+ scope: FluidObject,
63
+ codeLoader: ICodeDetailsLoader,
64
+ codeDetails: IFluidCodeDetails,
65
+ baseSnapshot: ISnapshotTree | undefined,
66
+ deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
67
+ quorum: IQuorum,
68
+ loader: ILoader,
69
+ submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
70
+ submitSummaryFn: (summaryOp: ISummaryContent, referenceSequenceNumber?: number) => number,
71
+ submitBatchFn: (batch: IBatchMessage[], referenceSequenceNumber?: number) => number,
72
+ submitSignalFn: (contents: any) => void,
73
+ disposeFn: (error?: ICriticalContainerError) => void,
74
+ closeFn: (error?: ICriticalContainerError) => void,
75
+ version: string,
76
+ updateDirtyContainerState: (dirty: boolean) => void,
77
+ existing: boolean,
78
+ pendingLocalState?: unknown,
79
+ ): Promise<ContainerContext> {
80
+ const context = new ContainerContext(
81
+ container,
82
+ scope,
83
+ codeLoader,
84
+ codeDetails,
85
+ baseSnapshot,
86
+ deltaManager,
87
+ quorum,
88
+ loader,
89
+ submitFn,
90
+ submitSummaryFn,
91
+ submitBatchFn,
92
+ submitSignalFn,
93
+ disposeFn,
94
+ closeFn,
95
+ version,
96
+ updateDirtyContainerState,
97
+ existing,
98
+ pendingLocalState,
99
+ );
100
+ await context.instantiateRuntime(existing);
101
+ return context;
102
+ }
103
+
104
+ public readonly taggedLogger: ITelemetryLogger;
105
+ public readonly supportedFeatures: ReadonlyMap<string, unknown>;
106
+
107
+ public get clientId(): string | undefined {
108
+ return this.container.clientId;
109
+ }
110
+
111
+ /**
112
+ * DISCLAIMER: this id is only for telemetry purposes. Not suitable for any other usages.
113
+ */
114
+ public get id(): string {
115
+ const resolvedUrl = this.container.resolvedUrl;
116
+ if (isFluidResolvedUrl(resolvedUrl)) {
117
+ return resolvedUrl.id;
118
+ }
119
+ return "";
120
+ }
121
+
122
+ public get clientDetails(): IClientDetails {
123
+ return this.container.clientDetails;
124
+ }
125
+
126
+ private _connected: boolean;
127
+ /**
128
+ * When true, ops are free to flow
129
+ * When false, ops should be kept as pending or rejected
130
+ */
131
+ public get connected(): boolean {
132
+ return this._connected;
133
+ }
134
+
135
+ public get canSummarize(): boolean {
136
+ return "summarize" in this.runtime;
137
+ }
138
+
139
+ public get serviceConfiguration(): IClientConfiguration | undefined {
140
+ return this.container.serviceConfiguration;
141
+ }
142
+
143
+ public get audience(): IAudience {
144
+ return this.container.audience;
145
+ }
146
+
147
+ public get options(): ILoaderOptions {
148
+ return this.container.options;
149
+ }
150
+
151
+ public get baseSnapshot() {
152
+ return this._baseSnapshot;
153
+ }
154
+
155
+ public get storage(): IDocumentStorageService {
156
+ return this.container.storage;
157
+ }
158
+
159
+ private _runtime: IRuntime | undefined;
160
+ private get runtime() {
161
+ if (this._runtime === undefined) {
162
+ throw new Error("Attempted to access runtime before it was defined");
163
+ }
164
+ return this._runtime;
165
+ }
166
+
167
+ private _disposed = false;
168
+
169
+ public get disposed() {
170
+ return this._disposed;
171
+ }
172
+
173
+ public get codeDetails() {
174
+ return this._codeDetails;
175
+ }
176
+
177
+ private readonly _quorum: IQuorum;
178
+ public get quorum(): IQuorumClients {
179
+ return this._quorum;
180
+ }
181
+
182
+ private readonly _fluidModuleP: Promise<IFluidModuleWithDetails>;
183
+
184
+ /**
185
+ * {@inheritDoc @fluidframework/container-definitions#IContainerContext.getEntryPoint}
186
+ */
187
+ public async getEntryPoint?(): Promise<FluidObject | undefined> {
188
+ if (this._disposed) {
189
+ throw new UsageError("The context is already disposed");
190
+ }
191
+ if (this._runtime !== undefined) {
192
+ return this._runtime?.getEntryPoint?.();
193
+ }
194
+ return new Promise<FluidObject | undefined>((resolve, reject) => {
195
+ const runtimeInstantiatedHandler = () => {
196
+ assert(
197
+ this._runtime !== undefined,
198
+ 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */,
199
+ );
200
+ resolve(this._runtime.getEntryPoint?.());
201
+ this.lifecycleEvents.off("disposed", disposedHandler);
202
+ };
203
+ const disposedHandler = () => {
204
+ reject(new Error("ContainerContext was disposed"));
205
+ this.lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
206
+ };
207
+ this.lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
208
+ this.lifecycleEvents.once("disposed", disposedHandler);
209
+ });
210
+ }
211
+
212
+ /**
213
+ * Emits events about the container context's lifecycle.
214
+ * Use it to coordinate things inside the ContainerContext class.
215
+ */
216
+ private readonly lifecycleEvents = new TypedEventEmitter<ContextLifecycleEvents>();
217
+
218
+ constructor(
219
+ private readonly container: Container,
220
+ public readonly scope: FluidObject,
221
+ private readonly codeLoader: ICodeDetailsLoader,
222
+ private readonly _codeDetails: IFluidCodeDetails,
223
+ private readonly _baseSnapshot: ISnapshotTree | undefined,
224
+ public readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
225
+ quorum: IQuorum,
226
+ public readonly loader: ILoader,
227
+ public readonly submitFn: (
228
+ type: MessageType,
229
+ contents: any,
230
+ batch: boolean,
231
+ appData: any,
232
+ ) => number,
233
+ public readonly submitSummaryFn: (
234
+ summaryOp: ISummaryContent,
235
+ referenceSequenceNumber?: number,
236
+ ) => number,
237
+ /** @returns clientSequenceNumber of last message in a batch */
238
+ public readonly submitBatchFn: (
239
+ batch: IBatchMessage[],
240
+ referenceSequenceNumber?: number,
241
+ ) => number,
242
+ public readonly submitSignalFn: (contents: any) => void,
243
+ public readonly disposeFn: (error?: ICriticalContainerError) => void,
244
+ public readonly closeFn: (error?: ICriticalContainerError) => void,
245
+ public readonly version: string,
246
+ public readonly updateDirtyContainerState: (dirty: boolean) => void,
247
+ public readonly existing: boolean,
248
+ public readonly pendingLocalState?: unknown,
249
+ ) {
250
+ this._connected = this.container.connected;
251
+ this._quorum = quorum;
252
+ this.taggedLogger = container.subLogger;
253
+ this._fluidModuleP = new LazyPromise<IFluidModuleWithDetails>(async () =>
254
+ this.loadCodeModule(_codeDetails),
255
+ );
256
+
257
+ this.supportedFeatures = new Map([
258
+ /**
259
+ * This version of the loader accepts `referenceSequenceNumber`, provided by the container runtime,
260
+ * as a parameter to the `submitBatchFn` and `submitSummaryFn` functions.
261
+ * This is then used to set the reference sequence numbers of the submitted ops in the DeltaManager.
262
+ */
263
+ ["referenceSequenceNumbers", true],
264
+ ]);
265
+ this.attachListener();
266
+ }
267
+
268
+ /**
269
+ * @deprecated Temporary migratory API, to be removed when customers no longer need it.
270
+ * When removed, `ContainerContext` should only take an {@link @fluidframework/container-definitions#IQuorumClients}
271
+ * rather than an {@link @fluidframework/protocol-definitions#IQuorum}.
272
+ * See {@link @fluidframework/container-definitions#IContainerContext} for more details.
273
+ */
274
+ public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
275
+ return (this._quorum.get("code") ?? this._quorum.get("code2")) as
276
+ | IFluidCodeDetails
277
+ | undefined;
278
+ }
279
+
280
+ public dispose(error?: Error): void {
281
+ if (this._disposed) {
282
+ return;
283
+ }
284
+ this._disposed = true;
285
+
286
+ this.lifecycleEvents.emit("disposed");
287
+ this.runtime.dispose(error);
288
+ this._quorum.dispose();
289
+ this.deltaManager.dispose();
290
+ }
291
+
292
+ public getLoadedFromVersion(): IVersion | undefined {
293
+ return this.container.loadedFromVersion;
294
+ }
295
+
296
+ public get attachState(): AttachState {
297
+ return this.container.attachState;
298
+ }
299
+
300
+ /**
301
+ * Create a summary. Used when attaching or serializing a detached container.
302
+ *
303
+ * @param blobRedirectTable - A table passed during the attach process. While detached, blob upload is supported
304
+ * using IDs generated locally. After attach, these IDs cannot be used, so this table maps the old local IDs to the
305
+ * new storage IDs so requests can be redirected.
306
+ */
307
+ public createSummary(blobRedirectTable?: Map<string, string>): ISummaryTree {
308
+ return this.runtime.createSummary(blobRedirectTable);
309
+ }
310
+
311
+ public setConnectionState(connected: boolean, clientId?: string) {
312
+ const runtime = this.runtime;
313
+ this._connected = connected;
314
+ runtime.setConnectionState(connected, clientId);
315
+ }
316
+
317
+ public process(message: ISequencedDocumentMessage, local: boolean) {
318
+ this.runtime.process(message, local);
319
+ }
320
+
321
+ public processSignal(message: ISignalMessage, local: boolean) {
322
+ this.runtime.processSignal(message, local);
323
+ }
324
+
325
+ public async request(path: IRequest): Promise<IResponse> {
326
+ return this.runtime.request(path);
327
+ }
328
+
329
+ public async getAbsoluteUrl(relativeUrl: string): Promise<string | undefined> {
330
+ return this.container.getAbsoluteUrl(relativeUrl);
331
+ }
332
+
333
+ public getPendingLocalState(): unknown {
334
+ return this.runtime.getPendingLocalState();
335
+ }
336
+
337
+ /**
338
+ * Determines if the current code details of the context
339
+ * satisfy the incoming constraint code details
340
+ */
341
+ public async satisfies(constraintCodeDetails: IFluidCodeDetails) {
342
+ const comparers: IFluidCodeDetailsComparer[] = [];
343
+
344
+ const maybeCompareCodeLoader = this.codeLoader;
345
+ if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
346
+ comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
347
+ }
348
+
349
+ const moduleWithDetails = await this._fluidModuleP;
350
+ const maybeCompareExport: Partial<IProvideFluidCodeDetailsComparer> | undefined =
351
+ moduleWithDetails.module?.fluidExport;
352
+ if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
353
+ comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
354
+ }
355
+
356
+ // if there are not comparers it is not possible to know
357
+ // if the current satisfy the incoming, so return false,
358
+ // as assuming they do not satisfy is safer .e.g we will
359
+ // reload, rather than potentially running with
360
+ // incompatible code
361
+ if (comparers.length === 0) {
362
+ return false;
363
+ }
364
+
365
+ for (const comparer of comparers) {
366
+ const satisfies = await comparer.satisfies(
367
+ moduleWithDetails.details,
368
+ constraintCodeDetails,
369
+ );
370
+ if (satisfies === false) {
371
+ return false;
372
+ }
373
+ }
374
+ return true;
375
+ }
376
+
377
+ public async notifyOpReplay(message: ISequencedDocumentMessage): Promise<void> {
378
+ return this.runtime.notifyOpReplay?.(message);
379
+ }
380
+
381
+ // #region private
382
+
383
+ private async getRuntimeFactory(): Promise<IRuntimeFactory> {
384
+ const fluidExport: FluidObject<IProvideRuntimeFactory> | undefined = (
385
+ await this._fluidModuleP
386
+ ).module?.fluidExport;
387
+ const runtimeFactory = fluidExport?.IRuntimeFactory;
388
+ if (runtimeFactory === undefined) {
389
+ throw new Error(PackageNotFactoryError);
390
+ }
391
+
392
+ return runtimeFactory;
393
+ }
394
+
395
+ private async instantiateRuntime(existing: boolean) {
396
+ const runtimeFactory = await this.getRuntimeFactory();
397
+ this._runtime = await PerformanceEvent.timedExecAsync(
398
+ this.taggedLogger,
399
+ { eventName: "InstantiateRuntime" },
400
+ async () => runtimeFactory.instantiateRuntime(this, existing),
401
+ );
402
+ this.lifecycleEvents.emit("runtimeInstantiated");
403
+ }
404
+
405
+ private attachListener() {
406
+ this.container.once("attaching", () => {
407
+ this.runtime.setAttachState(AttachState.Attaching);
408
+ });
409
+ this.container.once("attached", () => {
410
+ this.runtime.setAttachState(AttachState.Attached);
411
+ });
412
+ }
413
+
414
+ private async loadCodeModule(codeDetails: IFluidCodeDetails): Promise<IFluidModuleWithDetails> {
415
+ const loadCodeResult = await PerformanceEvent.timedExecAsync(
416
+ this.taggedLogger,
417
+ { eventName: "CodeLoad" },
418
+ async () => this.codeLoader.load(codeDetails),
419
+ );
420
+
421
+ if ("module" in loadCodeResult) {
422
+ const { module, details } = loadCodeResult;
423
+ return {
424
+ module,
425
+ details: details ?? codeDetails,
426
+ };
427
+ } else {
428
+ // If "module" is not in the result, we are using a legacy ICodeLoader. Fix the result up with details.
429
+ // Once usage drops to 0 we can remove this compat path.
430
+ this.taggedLogger.sendTelemetryEvent({ eventName: "LegacyCodeLoader" });
431
+ return loadCodeResult;
432
+ }
433
+ }
434
+ // #endregion
363
435
  }