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

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 (155) hide show
  1. package/.eslintrc.js +18 -21
  2. package/.mocharc.js +2 -2
  3. package/README.md +58 -40
  4. package/api-extractor.json +2 -2
  5. package/dist/audience.d.ts +0 -1
  6. package/dist/audience.d.ts.map +1 -1
  7. package/dist/audience.js.map +1 -1
  8. package/dist/catchUpMonitor.d.ts.map +1 -1
  9. package/dist/catchUpMonitor.js.map +1 -1
  10. package/dist/collabWindowTracker.d.ts.map +1 -1
  11. package/dist/collabWindowTracker.js.map +1 -1
  12. package/dist/connectionManager.d.ts +5 -5
  13. package/dist/connectionManager.d.ts.map +1 -1
  14. package/dist/connectionManager.js +64 -33
  15. package/dist/connectionManager.js.map +1 -1
  16. package/dist/connectionState.d.ts.map +1 -1
  17. package/dist/connectionState.js.map +1 -1
  18. package/dist/connectionStateHandler.d.ts +3 -3
  19. package/dist/connectionStateHandler.d.ts.map +1 -1
  20. package/dist/connectionStateHandler.js +43 -21
  21. package/dist/connectionStateHandler.js.map +1 -1
  22. package/dist/container.d.ts +20 -1
  23. package/dist/container.d.ts.map +1 -1
  24. package/dist/container.js +187 -54
  25. package/dist/container.js.map +1 -1
  26. package/dist/containerContext.d.ts +3 -2
  27. package/dist/containerContext.d.ts.map +1 -1
  28. package/dist/containerContext.js +11 -10
  29. package/dist/containerContext.js.map +1 -1
  30. package/dist/containerStorageAdapter.d.ts.map +1 -1
  31. package/dist/containerStorageAdapter.js +2 -4
  32. package/dist/containerStorageAdapter.js.map +1 -1
  33. package/dist/contracts.d.ts +1 -1
  34. package/dist/contracts.d.ts.map +1 -1
  35. package/dist/contracts.js.map +1 -1
  36. package/dist/deltaManager.d.ts +18 -6
  37. package/dist/deltaManager.d.ts.map +1 -1
  38. package/dist/deltaManager.js +108 -35
  39. package/dist/deltaManager.js.map +1 -1
  40. package/dist/deltaManagerProxy.d.ts.map +1 -1
  41. package/dist/deltaManagerProxy.js.map +1 -1
  42. package/dist/deltaQueue.d.ts.map +1 -1
  43. package/dist/deltaQueue.js +4 -2
  44. package/dist/deltaQueue.js.map +1 -1
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/loader.d.ts +3 -3
  49. package/dist/loader.d.ts.map +1 -1
  50. package/dist/loader.js +16 -8
  51. package/dist/loader.js.map +1 -1
  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.map +1 -1
  56. package/dist/protocol.js +2 -1
  57. package/dist/protocol.js.map +1 -1
  58. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  59. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  60. package/dist/quorum.d.ts.map +1 -1
  61. package/dist/quorum.js.map +1 -1
  62. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  63. package/dist/retriableDocumentStorageService.js +6 -2
  64. package/dist/retriableDocumentStorageService.js.map +1 -1
  65. package/dist/utils.d.ts.map +1 -1
  66. package/dist/utils.js +6 -4
  67. package/dist/utils.js.map +1 -1
  68. package/lib/audience.d.ts +0 -1
  69. package/lib/audience.d.ts.map +1 -1
  70. package/lib/audience.js.map +1 -1
  71. package/lib/catchUpMonitor.d.ts.map +1 -1
  72. package/lib/catchUpMonitor.js.map +1 -1
  73. package/lib/collabWindowTracker.d.ts.map +1 -1
  74. package/lib/collabWindowTracker.js.map +1 -1
  75. package/lib/connectionManager.d.ts +5 -5
  76. package/lib/connectionManager.d.ts.map +1 -1
  77. package/lib/connectionManager.js +66 -35
  78. package/lib/connectionManager.js.map +1 -1
  79. package/lib/connectionState.d.ts.map +1 -1
  80. package/lib/connectionState.js.map +1 -1
  81. package/lib/connectionStateHandler.d.ts +3 -3
  82. package/lib/connectionStateHandler.d.ts.map +1 -1
  83. package/lib/connectionStateHandler.js +43 -21
  84. package/lib/connectionStateHandler.js.map +1 -1
  85. package/lib/container.d.ts +20 -1
  86. package/lib/container.d.ts.map +1 -1
  87. package/lib/container.js +191 -58
  88. package/lib/container.js.map +1 -1
  89. package/lib/containerContext.d.ts +3 -2
  90. package/lib/containerContext.d.ts.map +1 -1
  91. package/lib/containerContext.js +11 -10
  92. package/lib/containerContext.js.map +1 -1
  93. package/lib/containerStorageAdapter.d.ts.map +1 -1
  94. package/lib/containerStorageAdapter.js +2 -4
  95. package/lib/containerStorageAdapter.js.map +1 -1
  96. package/lib/contracts.d.ts +1 -1
  97. package/lib/contracts.d.ts.map +1 -1
  98. package/lib/contracts.js.map +1 -1
  99. package/lib/deltaManager.d.ts +18 -6
  100. package/lib/deltaManager.d.ts.map +1 -1
  101. package/lib/deltaManager.js +110 -37
  102. package/lib/deltaManager.js.map +1 -1
  103. package/lib/deltaManagerProxy.d.ts.map +1 -1
  104. package/lib/deltaManagerProxy.js.map +1 -1
  105. package/lib/deltaQueue.d.ts.map +1 -1
  106. package/lib/deltaQueue.js +4 -2
  107. package/lib/deltaQueue.js.map +1 -1
  108. package/lib/index.d.ts +1 -1
  109. package/lib/index.d.ts.map +1 -1
  110. package/lib/index.js.map +1 -1
  111. package/lib/loader.d.ts +3 -3
  112. package/lib/loader.d.ts.map +1 -1
  113. package/lib/loader.js +16 -8
  114. package/lib/loader.js.map +1 -1
  115. package/lib/packageVersion.d.ts +1 -1
  116. package/lib/packageVersion.js +1 -1
  117. package/lib/packageVersion.js.map +1 -1
  118. package/lib/protocol.d.ts.map +1 -1
  119. package/lib/protocol.js +2 -1
  120. package/lib/protocol.js.map +1 -1
  121. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  122. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  123. package/lib/quorum.d.ts.map +1 -1
  124. package/lib/quorum.js.map +1 -1
  125. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  126. package/lib/retriableDocumentStorageService.js +6 -2
  127. package/lib/retriableDocumentStorageService.js.map +1 -1
  128. package/lib/utils.d.ts.map +1 -1
  129. package/lib/utils.js +6 -4
  130. package/lib/utils.js.map +1 -1
  131. package/package.json +22 -19
  132. package/prettier.config.cjs +1 -1
  133. package/src/audience.ts +51 -46
  134. package/src/catchUpMonitor.ts +39 -37
  135. package/src/collabWindowTracker.ts +75 -70
  136. package/src/connectionManager.ts +1009 -941
  137. package/src/connectionState.ts +19 -19
  138. package/src/connectionStateHandler.ts +544 -462
  139. package/src/container.ts +2039 -1784
  140. package/src/containerContext.ts +353 -344
  141. package/src/containerStorageAdapter.ts +163 -153
  142. package/src/contracts.ts +155 -153
  143. package/src/deltaManager.ts +1069 -945
  144. package/src/deltaManagerProxy.ts +143 -137
  145. package/src/deltaQueue.ts +155 -151
  146. package/src/index.ts +14 -17
  147. package/src/loader.ts +427 -422
  148. package/src/packageVersion.ts +1 -1
  149. package/src/protocol.ts +93 -87
  150. package/src/protocolTreeDocumentStorageService.ts +30 -33
  151. package/src/quorum.ts +34 -34
  152. package/src/retriableDocumentStorageService.ts +118 -102
  153. package/src/utils.ts +89 -82
  154. package/tsconfig.esnext.json +6 -6
  155. package/tsconfig.json +8 -12
package/src/loader.ts CHANGED
@@ -6,44 +6,44 @@
6
6
  import { v4 as uuid } from "uuid";
7
7
  import { ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
8
8
  import {
9
- FluidObject,
10
- IFluidRouter,
11
- IRequest,
12
- IRequestHeader,
13
- IResponse,
9
+ FluidObject,
10
+ IFluidRouter,
11
+ IRequest,
12
+ IRequestHeader,
13
+ IResponse,
14
14
  } from "@fluidframework/core-interfaces";
15
15
  import {
16
- IContainer,
17
- IFluidModule,
18
- IHostLoader,
19
- ILoader,
20
- ILoaderOptions as ILoaderOptions1,
21
- LoaderHeader,
22
- IProvideFluidCodeDetailsComparer,
23
- IFluidCodeDetails,
16
+ IContainer,
17
+ IFluidModule,
18
+ IHostLoader,
19
+ ILoader,
20
+ ILoaderOptions as ILoaderOptions1,
21
+ LoaderHeader,
22
+ IProvideFluidCodeDetailsComparer,
23
+ IFluidCodeDetails,
24
24
  } from "@fluidframework/container-definitions";
25
25
  import {
26
- ChildLogger,
27
- DebugLogger,
28
- IConfigProviderBase,
29
- loggerToMonitoringContext,
30
- mixinMonitoringContext,
31
- MonitoringContext,
32
- PerformanceEvent,
33
- sessionStorageConfigProvider,
26
+ ChildLogger,
27
+ DebugLogger,
28
+ IConfigProviderBase,
29
+ loggerToMonitoringContext,
30
+ mixinMonitoringContext,
31
+ MonitoringContext,
32
+ PerformanceEvent,
33
+ sessionStorageConfigProvider,
34
34
  } from "@fluidframework/telemetry-utils";
35
35
  import {
36
- IDocumentServiceFactory,
37
- IDocumentStorageService,
38
- IFluidResolvedUrl,
39
- IResolvedUrl,
40
- IUrlResolver,
36
+ IDocumentServiceFactory,
37
+ IDocumentStorageService,
38
+ IFluidResolvedUrl,
39
+ IResolvedUrl,
40
+ IUrlResolver,
41
41
  } from "@fluidframework/driver-definitions";
42
42
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
43
43
  import {
44
- ensureFluidResolvedUrl,
45
- MultiUrlResolver,
46
- MultiDocumentServiceFactory,
44
+ ensureFluidResolvedUrl,
45
+ MultiUrlResolver,
46
+ MultiDocumentServiceFactory,
47
47
  } from "@fluidframework/driver-utils";
48
48
  import { Container, IPendingContainerState } from "./container";
49
49
  import { IParsedUrl, parseUrl } from "./utils";
@@ -51,84 +51,82 @@ import { pkgVersion } from "./packageVersion";
51
51
  import { ProtocolHandlerBuilder } from "./protocol";
52
52
 
53
53
  function canUseCache(request: IRequest): boolean {
54
- if (request.headers === undefined) {
55
- return true;
56
- }
54
+ if (request.headers === undefined) {
55
+ return true;
56
+ }
57
57
 
58
- return request.headers[LoaderHeader.cache] !== false;
58
+ return request.headers[LoaderHeader.cache] !== false;
59
59
  }
60
60
 
61
61
  export class RelativeLoader implements ILoader {
62
- constructor(
63
- private readonly container: Container,
64
- private readonly loader: ILoader | undefined,
65
- ) {
66
- }
67
-
68
- public get IFluidRouter(): IFluidRouter { return this; }
69
-
70
- public async resolve(request: IRequest): Promise<IContainer> {
71
- if (request.url.startsWith("/")) {
72
- if (canUseCache(request)) {
73
- return this.container;
74
- } else {
75
- const resolvedUrl = this.container.resolvedUrl;
76
- ensureFluidResolvedUrl(resolvedUrl);
77
- const container = await Container.load(
78
- this.loader as Loader,
79
- {
80
- canReconnect: request.headers?.[LoaderHeader.reconnect],
81
- clientDetailsOverride: request.headers?.[LoaderHeader.clientDetails],
82
- resolvedUrl: { ...resolvedUrl },
83
- version: request.headers?.[LoaderHeader.version] ?? undefined,
84
- loadMode: request.headers?.[LoaderHeader.loadMode],
85
- },
86
- );
87
- return container;
88
- }
89
- }
90
-
91
- if (this.loader === undefined) {
92
- throw new Error("Cannot resolve external containers");
93
- }
94
- return this.loader.resolve(request);
95
- }
96
-
97
- public async request(request: IRequest): Promise<IResponse> {
98
- if (request.url.startsWith("/")) {
99
- const container = await this.resolve(request);
100
- return container.request(request);
101
- }
102
-
103
- if (this.loader === undefined) {
104
- return {
105
- status: 404,
106
- value: "Cannot request external containers",
107
- mimeType: "plain/text",
108
- };
109
- }
110
- return this.loader.request(request);
111
- }
62
+ constructor(
63
+ private readonly container: Container,
64
+ private readonly loader: ILoader | undefined,
65
+ ) {}
66
+
67
+ public get IFluidRouter(): IFluidRouter {
68
+ return this;
69
+ }
70
+
71
+ public async resolve(request: IRequest): Promise<IContainer> {
72
+ if (request.url.startsWith("/")) {
73
+ if (canUseCache(request)) {
74
+ return this.container;
75
+ } else {
76
+ const resolvedUrl = this.container.resolvedUrl;
77
+ ensureFluidResolvedUrl(resolvedUrl);
78
+ const container = await Container.load(this.loader as Loader, {
79
+ canReconnect: request.headers?.[LoaderHeader.reconnect],
80
+ clientDetailsOverride: request.headers?.[LoaderHeader.clientDetails],
81
+ resolvedUrl: { ...resolvedUrl },
82
+ version: request.headers?.[LoaderHeader.version] ?? undefined,
83
+ loadMode: request.headers?.[LoaderHeader.loadMode],
84
+ });
85
+ return container;
86
+ }
87
+ }
88
+
89
+ if (this.loader === undefined) {
90
+ throw new Error("Cannot resolve external containers");
91
+ }
92
+ return this.loader.resolve(request);
93
+ }
94
+
95
+ public async request(request: IRequest): Promise<IResponse> {
96
+ if (request.url.startsWith("/")) {
97
+ const container = await this.resolve(request);
98
+ return container.request(request);
99
+ }
100
+
101
+ if (this.loader === undefined) {
102
+ return {
103
+ status: 404,
104
+ value: "Cannot request external containers",
105
+ mimeType: "plain/text",
106
+ };
107
+ }
108
+ return this.loader.request(request);
109
+ }
112
110
  }
113
111
 
114
112
  function createCachedResolver(resolver: IUrlResolver) {
115
- const cacheResolver = Object.create(resolver) as IUrlResolver;
116
- const resolveCache = new Map<string, Promise<IResolvedUrl | undefined>>();
117
- cacheResolver.resolve = async (request: IRequest): Promise<IResolvedUrl | undefined> => {
118
- if (!canUseCache(request)) {
119
- return resolver.resolve(request);
120
- }
121
- if (!resolveCache.has(request.url)) {
122
- resolveCache.set(request.url, resolver.resolve(request));
123
- }
124
-
125
- return resolveCache.get(request.url);
126
- };
127
- return cacheResolver;
113
+ const cacheResolver = Object.create(resolver) as IUrlResolver;
114
+ const resolveCache = new Map<string, Promise<IResolvedUrl | undefined>>();
115
+ cacheResolver.resolve = async (request: IRequest): Promise<IResolvedUrl | undefined> => {
116
+ if (!canUseCache(request)) {
117
+ return resolver.resolve(request);
118
+ }
119
+ if (!resolveCache.has(request.url)) {
120
+ resolveCache.set(request.url, resolver.resolve(request));
121
+ }
122
+
123
+ return resolveCache.get(request.url);
124
+ };
125
+ return cacheResolver;
128
126
  }
129
127
 
130
128
  export interface ILoaderOptions extends ILoaderOptions1 {
131
- summarizeProtocolTree?: boolean;
129
+ summarizeProtocolTree?: boolean;
132
130
  }
133
131
 
134
132
  /**
@@ -138,14 +136,14 @@ export interface ILoaderOptions extends ILoaderOptions1 {
138
136
  * Encapsulates a module entry point with corresponding code details.
139
137
  */
140
138
  export interface IFluidModuleWithDetails {
141
- /** Fluid code module that implements the runtime factory needed to instantiate the container runtime. */
142
- module: IFluidModule;
143
- /**
144
- * Code details associated with the module. Represents a document schema this module supports.
145
- * If the code loader implements the {@link @fluidframework/core-interfaces#IFluidCodeDetailsComparer} interface,
146
- * it'll be called to determine whether the module code details satisfy the new code proposal in the quorum.
147
- */
148
- details: IFluidCodeDetails;
139
+ /** Fluid code module that implements the runtime factory needed to instantiate the container runtime. */
140
+ module: IFluidModule;
141
+ /**
142
+ * Code details associated with the module. Represents a document schema this module supports.
143
+ * If the code loader implements the {@link @fluidframework/core-interfaces#IFluidCodeDetailsComparer} interface,
144
+ * it'll be called to determine whether the module code details satisfy the new code proposal in the quorum.
145
+ */
146
+ details: IFluidCodeDetails;
149
147
  }
150
148
 
151
149
  /**
@@ -154,116 +152,115 @@ export interface IFluidModuleWithDetails {
154
152
  * Fluid code loader resolves a code module matching the document schema, i.e. code details, such as
155
153
  * a package name and package version range.
156
154
  */
157
- export interface ICodeDetailsLoader
158
- extends Partial<IProvideFluidCodeDetailsComparer> {
159
- /**
160
- * Load the code module (package) that is capable to interact with the document.
161
- *
162
- * @param source - Code proposal that articulates the current schema the document is written in.
163
- * @returns - Code module entry point along with the code details associated with it.
164
- */
165
- load(source: IFluidCodeDetails): Promise<IFluidModuleWithDetails>;
155
+ export interface ICodeDetailsLoader extends Partial<IProvideFluidCodeDetailsComparer> {
156
+ /**
157
+ * Load the code module (package) that is capable to interact with the document.
158
+ *
159
+ * @param source - Code proposal that articulates the current schema the document is written in.
160
+ * @returns - Code module entry point along with the code details associated with it.
161
+ */
162
+ load(source: IFluidCodeDetails): Promise<IFluidModuleWithDetails>;
166
163
  }
167
164
 
168
165
  /**
169
166
  * Services and properties necessary for creating a loader
170
167
  */
171
168
  export interface ILoaderProps {
172
- /**
173
- * The url resolver used by the loader for resolving external urls
174
- * into Fluid urls such that the container specified by the
175
- * external url can be loaded.
176
- */
177
- readonly urlResolver: IUrlResolver;
178
- /**
179
- * The document service factory take the Fluid url provided
180
- * by the resolved url and constructs all the necessary services
181
- * for communication with the container's server.
182
- */
183
- readonly documentServiceFactory: IDocumentServiceFactory;
184
- /**
185
- * The code loader handles loading the necessary code
186
- * for running a container once it is loaded.
187
- */
188
- readonly codeLoader: ICodeDetailsLoader;
189
-
190
- /**
191
- * A property bag of options used by various layers
192
- * to control features
193
- */
194
- readonly options?: ILoaderOptions;
195
-
196
- /**
197
- * Scope is provided to all container and is a set of shared
198
- * services for container's to integrate with their host environment.
199
- */
200
- readonly scope?: FluidObject;
201
-
202
- /**
203
- * The logger that all telemetry should be pushed to.
204
- */
205
- readonly logger?: ITelemetryBaseLogger;
206
-
207
- /**
208
- * Blobs storage for detached containers.
209
- */
210
- readonly detachedBlobStorage?: IDetachedBlobStorage;
211
-
212
- /**
213
- * The configuration provider which may be used to control features.
214
- */
215
- readonly configProvider?: IConfigProviderBase;
216
-
217
- /**
218
- * Optional property for allowing the container to use a custom
219
- * protocol implementation for handling the quorum and/or the audience.
220
- */
221
- readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
169
+ /**
170
+ * The url resolver used by the loader for resolving external urls
171
+ * into Fluid urls such that the container specified by the
172
+ * external url can be loaded.
173
+ */
174
+ readonly urlResolver: IUrlResolver;
175
+ /**
176
+ * The document service factory take the Fluid url provided
177
+ * by the resolved url and constructs all the necessary services
178
+ * for communication with the container's server.
179
+ */
180
+ readonly documentServiceFactory: IDocumentServiceFactory;
181
+ /**
182
+ * The code loader handles loading the necessary code
183
+ * for running a container once it is loaded.
184
+ */
185
+ readonly codeLoader: ICodeDetailsLoader;
186
+
187
+ /**
188
+ * A property bag of options used by various layers
189
+ * to control features
190
+ */
191
+ readonly options?: ILoaderOptions;
192
+
193
+ /**
194
+ * Scope is provided to all container and is a set of shared
195
+ * services for container's to integrate with their host environment.
196
+ */
197
+ readonly scope?: FluidObject;
198
+
199
+ /**
200
+ * The logger that all telemetry should be pushed to.
201
+ */
202
+ readonly logger?: ITelemetryBaseLogger;
203
+
204
+ /**
205
+ * Blobs storage for detached containers.
206
+ */
207
+ readonly detachedBlobStorage?: IDetachedBlobStorage;
208
+
209
+ /**
210
+ * The configuration provider which may be used to control features.
211
+ */
212
+ readonly configProvider?: IConfigProviderBase;
213
+
214
+ /**
215
+ * Optional property for allowing the container to use a custom
216
+ * protocol implementation for handling the quorum and/or the audience.
217
+ */
218
+ readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
222
219
  }
223
220
 
224
221
  /**
225
222
  * Services and properties used by and exposed by the loader
226
223
  */
227
224
  export interface ILoaderServices {
228
- /**
229
- * The url resolver used by the loader for resolving external urls
230
- * into Fluid urls such that the container specified by the
231
- * external url can be loaded.
232
- */
233
- readonly urlResolver: IUrlResolver;
234
- /**
235
- * The document service factory take the Fluid url provided
236
- * by the resolved url and constructs all the necessary services
237
- * for communication with the container's server.
238
- */
239
- readonly documentServiceFactory: IDocumentServiceFactory;
240
- /**
241
- * The code loader handles loading the necessary code
242
- * for running a container once it is loaded.
243
- */
244
- readonly codeLoader: ICodeDetailsLoader;
245
-
246
- /**
247
- * A property bag of options used by various layers
248
- * to control features
249
- */
250
- readonly options: ILoaderOptions;
251
-
252
- /**
253
- * Scope is provided to all container and is a set of shared
254
- * services for container's to integrate with their host environment.
255
- */
256
- readonly scope: FluidObject;
257
-
258
- /**
259
- * The logger downstream consumers should construct their loggers from
260
- */
261
- readonly subLogger: ITelemetryLogger;
262
-
263
- /**
264
- * Blobs storage for detached containers.
265
- */
266
- readonly detachedBlobStorage?: IDetachedBlobStorage;
225
+ /**
226
+ * The url resolver used by the loader for resolving external urls
227
+ * into Fluid urls such that the container specified by the
228
+ * external url can be loaded.
229
+ */
230
+ readonly urlResolver: IUrlResolver;
231
+ /**
232
+ * The document service factory take the Fluid url provided
233
+ * by the resolved url and constructs all the necessary services
234
+ * for communication with the container's server.
235
+ */
236
+ readonly documentServiceFactory: IDocumentServiceFactory;
237
+ /**
238
+ * The code loader handles loading the necessary code
239
+ * for running a container once it is loaded.
240
+ */
241
+ readonly codeLoader: ICodeDetailsLoader;
242
+
243
+ /**
244
+ * A property bag of options used by various layers
245
+ * to control features
246
+ */
247
+ readonly options: ILoaderOptions;
248
+
249
+ /**
250
+ * Scope is provided to all container and is a set of shared
251
+ * services for container's to integrate with their host environment.
252
+ */
253
+ readonly scope: FluidObject;
254
+
255
+ /**
256
+ * The logger downstream consumers should construct their loggers from
257
+ */
258
+ readonly subLogger: ITelemetryLogger;
259
+
260
+ /**
261
+ * Blobs storage for detached containers.
262
+ */
263
+ readonly detachedBlobStorage?: IDetachedBlobStorage;
267
264
  }
268
265
 
269
266
  /**
@@ -271,228 +268,236 @@ export interface ILoaderServices {
271
268
  * blobs in detached containers.
272
269
  */
273
270
  export type IDetachedBlobStorage = Pick<IDocumentStorageService, "createBlob" | "readBlob"> & {
274
- size: number;
275
- /**
276
- * Return an array of all blob IDs present in storage
277
- */
278
- getBlobIds(): string[];
279
- };
271
+ size: number;
272
+ /**
273
+ * Return an array of all blob IDs present in storage
274
+ */
275
+ getBlobIds(): string[];
276
+ };
280
277
 
281
278
  /**
282
279
  * Manages Fluid resource loading
283
280
  */
284
281
  export class Loader implements IHostLoader {
285
- private readonly containers = new Map<string, Promise<Container>>();
286
- public readonly services: ILoaderServices;
287
- private readonly mc: MonitoringContext;
288
- private readonly protocolHandlerBuilder: ProtocolHandlerBuilder | undefined;
289
-
290
- constructor(loaderProps: ILoaderProps) {
291
- const scope: FluidObject<ILoader> = { ...loaderProps.scope };
292
- if (loaderProps.options?.provideScopeLoader !== false) {
293
- scope.ILoader = this;
294
- }
295
- const telemetryProps = {
296
- loaderId: uuid(),
297
- loaderVersion: pkgVersion,
298
- };
299
-
300
- const subMc = mixinMonitoringContext(
301
- DebugLogger.mixinDebugLogger("fluid:telemetry", loaderProps.logger, { all: telemetryProps }),
302
- sessionStorageConfigProvider.value,
303
- loaderProps.configProvider,
304
- );
305
-
306
- this.services = {
307
- urlResolver: createCachedResolver(MultiUrlResolver.create(loaderProps.urlResolver)),
308
- documentServiceFactory: MultiDocumentServiceFactory.create(loaderProps.documentServiceFactory),
309
- codeLoader: loaderProps.codeLoader,
310
- options: loaderProps.options ?? {},
311
- scope,
312
- subLogger: subMc.logger,
313
- detachedBlobStorage: loaderProps.detachedBlobStorage,
314
- };
315
- this.mc = loggerToMonitoringContext(
316
- ChildLogger.create(this.services.subLogger, "Loader"));
317
- this.protocolHandlerBuilder = loaderProps.protocolHandlerBuilder;
318
- }
319
-
320
- public get IFluidRouter(): IFluidRouter { return this; }
321
-
322
- public async createDetachedContainer(codeDetails: IFluidCodeDetails): Promise<IContainer> {
323
- const container = await Container.createDetached(
324
- this,
325
- codeDetails,
326
- this.protocolHandlerBuilder,
327
- );
328
-
329
- if (this.cachingEnabled) {
330
- container.once("attached", () => {
331
- ensureFluidResolvedUrl(container.resolvedUrl);
332
- const parsedUrl = parseUrl(container.resolvedUrl.url);
333
- if (parsedUrl !== undefined) {
334
- this.addToContainerCache(parsedUrl.id, Promise.resolve(container));
335
- }
336
- });
337
- }
338
-
339
- return container;
340
- }
341
-
342
- public async rehydrateDetachedContainerFromSnapshot(snapshot: string): Promise<IContainer> {
343
- return Container.rehydrateDetachedFromSnapshot(this, snapshot, this.protocolHandlerBuilder);
344
- }
345
-
346
- public async resolve(request: IRequest, pendingLocalState?: string): Promise<IContainer> {
347
- const eventName = pendingLocalState === undefined ? "Resolve" : "ResolveWithPendingState";
348
- return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName }, async () => {
349
- const resolved = await this.resolveCore(
350
- request,
351
- pendingLocalState !== undefined ? JSON.parse(pendingLocalState) : undefined,
352
- );
353
- return resolved.container;
354
- });
355
- }
356
-
357
- public async request(request: IRequest): Promise<IResponse> {
358
- return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => {
359
- const resolved = await this.resolveCore(request);
360
- return resolved.container.request({
361
- ...request,
362
- url: `${resolved.parsed.path}${resolved.parsed.query}`,
363
- });
364
- });
365
- }
366
-
367
- private getKeyForContainerCache(request: IRequest, parsedUrl: IParsedUrl): string {
368
- const key = request.headers?.[LoaderHeader.version] !== undefined
369
- ? `${parsedUrl.id}@${request.headers[LoaderHeader.version]}`
370
- : parsedUrl.id;
371
- return key;
372
- }
373
-
374
- private addToContainerCache(key: string, containerP: Promise<Container>) {
375
- this.containers.set(key, containerP);
376
- containerP.then((container) => {
377
- // If the container is closed or becomes closed after we resolve it, remove it from the cache.
378
- if (container.closed) {
379
- this.containers.delete(key);
380
- } else {
381
- container.once("closed", () => {
382
- this.containers.delete(key);
383
- });
384
- }
385
- }).catch((error) => {});
386
- }
387
-
388
- private async resolveCore(
389
- request: IRequest,
390
- pendingLocalState?: IPendingContainerState,
391
- ): Promise<{ container: Container; parsed: IParsedUrl; }> {
392
- const resolvedAsFluid = await this.services.urlResolver.resolve(request);
393
- ensureFluidResolvedUrl(resolvedAsFluid);
394
-
395
- // Parse URL into data stores
396
- const parsed = parseUrl(resolvedAsFluid.url);
397
- if (parsed === undefined) {
398
- throw new Error(`Invalid URL ${resolvedAsFluid.url}`);
399
- }
400
-
401
- if (pendingLocalState !== undefined) {
402
- const parsedPendingUrl = parseUrl(pendingLocalState.url);
403
- if (parsedPendingUrl?.id !== parsed.id ||
404
- parsedPendingUrl?.path.replace(/\/$/, "") !== parsed.path.replace(/\/$/, "")) {
405
- const message = `URL ${resolvedAsFluid.url} does not match pending state URL ${pendingLocalState.url}`;
406
- throw new Error(message);
407
- }
408
- }
409
-
410
- const { canCache, fromSequenceNumber } = this.parseHeader(parsed, request);
411
- const shouldCache = pendingLocalState !== undefined ? false : canCache;
412
-
413
- let container: Container;
414
- if (shouldCache) {
415
- const key = this.getKeyForContainerCache(request, parsed);
416
- const maybeContainer = await this.containers.get(key);
417
- if (maybeContainer !== undefined) {
418
- container = maybeContainer;
419
- } else {
420
- const containerP =
421
- this.loadContainer(
422
- request,
423
- resolvedAsFluid);
424
- this.addToContainerCache(key, containerP);
425
- container = await containerP;
426
- }
427
- } else {
428
- container =
429
- await this.loadContainer(
430
- request,
431
- resolvedAsFluid,
432
- pendingLocalState);
433
- }
434
-
435
- if (container.deltaManager.lastSequenceNumber <= fromSequenceNumber) {
436
- await new Promise<void>((resolve, reject) => {
437
- function opHandler(message: ISequencedDocumentMessage) {
438
- if (message.sequenceNumber > fromSequenceNumber) {
439
- resolve();
440
- container.removeListener("op", opHandler);
441
- }
442
- }
443
-
444
- container.on("op", opHandler);
445
- });
446
- }
447
-
448
- return { container, parsed };
449
- }
450
-
451
- private get cachingEnabled() {
452
- return this.services.options.cache !== false;
453
- }
454
-
455
- private canCacheForRequest(headers: IRequestHeader): boolean {
456
- return this.cachingEnabled && headers[LoaderHeader.cache] !== false;
457
- }
458
-
459
- private parseHeader(parsed: IParsedUrl, request: IRequest) {
460
- let fromSequenceNumber = -1;
461
-
462
- request.headers = request.headers ?? {};
463
-
464
- const headerSeqNum = request.headers[LoaderHeader.sequenceNumber];
465
- if (headerSeqNum !== undefined) {
466
- fromSequenceNumber = headerSeqNum;
467
- }
468
-
469
- // If set in both query string and headers, use query string
470
- request.headers[LoaderHeader.version] = parsed.version ?? request.headers[LoaderHeader.version];
471
-
472
- const canCache = this.canCacheForRequest(request.headers);
473
-
474
- return {
475
- canCache,
476
- fromSequenceNumber,
477
- };
478
- }
479
-
480
- private async loadContainer(
481
- request: IRequest,
482
- resolved: IFluidResolvedUrl,
483
- pendingLocalState?: IPendingContainerState,
484
- ): Promise<Container> {
485
- return Container.load(
486
- this,
487
- {
488
- canReconnect: request.headers?.[LoaderHeader.reconnect],
489
- clientDetailsOverride: request.headers?.[LoaderHeader.clientDetails],
490
- resolvedUrl: resolved,
491
- version: request.headers?.[LoaderHeader.version] ?? undefined,
492
- loadMode: request.headers?.[LoaderHeader.loadMode],
493
- },
494
- pendingLocalState,
495
- this.protocolHandlerBuilder,
496
- );
497
- }
282
+ private readonly containers = new Map<string, Promise<Container>>();
283
+ public readonly services: ILoaderServices;
284
+ private readonly mc: MonitoringContext;
285
+ private readonly protocolHandlerBuilder: ProtocolHandlerBuilder | undefined;
286
+
287
+ constructor(loaderProps: ILoaderProps) {
288
+ const scope: FluidObject<ILoader> = { ...loaderProps.scope };
289
+ if (loaderProps.options?.provideScopeLoader !== false) {
290
+ scope.ILoader = this;
291
+ }
292
+ const telemetryProps = {
293
+ loaderId: uuid(),
294
+ loaderVersion: pkgVersion,
295
+ };
296
+
297
+ const subMc = mixinMonitoringContext(
298
+ DebugLogger.mixinDebugLogger("fluid:telemetry", loaderProps.logger, {
299
+ all: telemetryProps,
300
+ }),
301
+ sessionStorageConfigProvider.value,
302
+ loaderProps.configProvider,
303
+ );
304
+
305
+ this.services = {
306
+ urlResolver: createCachedResolver(MultiUrlResolver.create(loaderProps.urlResolver)),
307
+ documentServiceFactory: MultiDocumentServiceFactory.create(
308
+ loaderProps.documentServiceFactory,
309
+ ),
310
+ codeLoader: loaderProps.codeLoader,
311
+ options: loaderProps.options ?? {},
312
+ scope,
313
+ subLogger: subMc.logger,
314
+ detachedBlobStorage: loaderProps.detachedBlobStorage,
315
+ };
316
+ this.mc = loggerToMonitoringContext(ChildLogger.create(this.services.subLogger, "Loader"));
317
+ this.protocolHandlerBuilder = loaderProps.protocolHandlerBuilder;
318
+ }
319
+
320
+ public get IFluidRouter(): IFluidRouter {
321
+ return this;
322
+ }
323
+
324
+ public async createDetachedContainer(codeDetails: IFluidCodeDetails): Promise<IContainer> {
325
+ const container = await Container.createDetached(
326
+ this,
327
+ codeDetails,
328
+ this.protocolHandlerBuilder,
329
+ );
330
+
331
+ if (this.cachingEnabled) {
332
+ container.once("attached", () => {
333
+ ensureFluidResolvedUrl(container.resolvedUrl);
334
+ const parsedUrl = parseUrl(container.resolvedUrl.url);
335
+ if (parsedUrl !== undefined) {
336
+ this.addToContainerCache(parsedUrl.id, Promise.resolve(container));
337
+ }
338
+ });
339
+ }
340
+
341
+ return container;
342
+ }
343
+
344
+ public async rehydrateDetachedContainerFromSnapshot(snapshot: string): Promise<IContainer> {
345
+ return Container.rehydrateDetachedFromSnapshot(this, snapshot, this.protocolHandlerBuilder);
346
+ }
347
+
348
+ public async resolve(request: IRequest, pendingLocalState?: string): Promise<IContainer> {
349
+ const eventName = pendingLocalState === undefined ? "Resolve" : "ResolveWithPendingState";
350
+ return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName }, async () => {
351
+ const resolved = await this.resolveCore(
352
+ request,
353
+ pendingLocalState !== undefined ? JSON.parse(pendingLocalState) : undefined,
354
+ );
355
+ return resolved.container;
356
+ });
357
+ }
358
+
359
+ public async request(request: IRequest): Promise<IResponse> {
360
+ return PerformanceEvent.timedExecAsync(
361
+ this.mc.logger,
362
+ { eventName: "Request" },
363
+ async () => {
364
+ const resolved = await this.resolveCore(request);
365
+ return resolved.container.request({
366
+ ...request,
367
+ url: `${resolved.parsed.path}${resolved.parsed.query}`,
368
+ });
369
+ },
370
+ );
371
+ }
372
+
373
+ private getKeyForContainerCache(request: IRequest, parsedUrl: IParsedUrl): string {
374
+ const key =
375
+ request.headers?.[LoaderHeader.version] !== undefined
376
+ ? `${parsedUrl.id}@${request.headers[LoaderHeader.version]}`
377
+ : parsedUrl.id;
378
+ return key;
379
+ }
380
+
381
+ private addToContainerCache(key: string, containerP: Promise<Container>) {
382
+ this.containers.set(key, containerP);
383
+ containerP
384
+ .then((container) => {
385
+ // If the container is closed or becomes closed after we resolve it, remove it from the cache.
386
+ if (container.closed) {
387
+ this.containers.delete(key);
388
+ } else {
389
+ container.once("closed", () => {
390
+ this.containers.delete(key);
391
+ });
392
+ }
393
+ })
394
+ .catch((error) => {});
395
+ }
396
+
397
+ private async resolveCore(
398
+ request: IRequest,
399
+ pendingLocalState?: IPendingContainerState,
400
+ ): Promise<{ container: Container; parsed: IParsedUrl }> {
401
+ const resolvedAsFluid = await this.services.urlResolver.resolve(request);
402
+ ensureFluidResolvedUrl(resolvedAsFluid);
403
+
404
+ // Parse URL into data stores
405
+ const parsed = parseUrl(resolvedAsFluid.url);
406
+ if (parsed === undefined) {
407
+ throw new Error(`Invalid URL ${resolvedAsFluid.url}`);
408
+ }
409
+
410
+ if (pendingLocalState !== undefined) {
411
+ const parsedPendingUrl = parseUrl(pendingLocalState.url);
412
+ if (
413
+ parsedPendingUrl?.id !== parsed.id ||
414
+ parsedPendingUrl?.path.replace(/\/$/, "") !== parsed.path.replace(/\/$/, "")
415
+ ) {
416
+ const message = `URL ${resolvedAsFluid.url} does not match pending state URL ${pendingLocalState.url}`;
417
+ throw new Error(message);
418
+ }
419
+ }
420
+
421
+ const { canCache, fromSequenceNumber } = this.parseHeader(parsed, request);
422
+ const shouldCache = pendingLocalState !== undefined ? false : canCache;
423
+
424
+ let container: Container;
425
+ if (shouldCache) {
426
+ const key = this.getKeyForContainerCache(request, parsed);
427
+ const maybeContainer = await this.containers.get(key);
428
+ if (maybeContainer !== undefined) {
429
+ container = maybeContainer;
430
+ } else {
431
+ const containerP = this.loadContainer(request, resolvedAsFluid);
432
+ this.addToContainerCache(key, containerP);
433
+ container = await containerP;
434
+ }
435
+ } else {
436
+ container = await this.loadContainer(request, resolvedAsFluid, pendingLocalState);
437
+ }
438
+
439
+ if (container.deltaManager.lastSequenceNumber <= fromSequenceNumber) {
440
+ await new Promise<void>((resolve, reject) => {
441
+ function opHandler(message: ISequencedDocumentMessage) {
442
+ if (message.sequenceNumber > fromSequenceNumber) {
443
+ resolve();
444
+ container.removeListener("op", opHandler);
445
+ }
446
+ }
447
+
448
+ container.on("op", opHandler);
449
+ });
450
+ }
451
+
452
+ return { container, parsed };
453
+ }
454
+
455
+ private get cachingEnabled() {
456
+ return this.services.options.cache !== false;
457
+ }
458
+
459
+ private canCacheForRequest(headers: IRequestHeader): boolean {
460
+ return this.cachingEnabled && headers[LoaderHeader.cache] !== false;
461
+ }
462
+
463
+ private parseHeader(parsed: IParsedUrl, request: IRequest) {
464
+ let fromSequenceNumber = -1;
465
+
466
+ request.headers = request.headers ?? {};
467
+
468
+ const headerSeqNum = request.headers[LoaderHeader.sequenceNumber];
469
+ if (headerSeqNum !== undefined) {
470
+ fromSequenceNumber = headerSeqNum;
471
+ }
472
+
473
+ // If set in both query string and headers, use query string
474
+ request.headers[LoaderHeader.version] =
475
+ parsed.version ?? request.headers[LoaderHeader.version];
476
+
477
+ const canCache = this.canCacheForRequest(request.headers);
478
+
479
+ return {
480
+ canCache,
481
+ fromSequenceNumber,
482
+ };
483
+ }
484
+
485
+ private async loadContainer(
486
+ request: IRequest,
487
+ resolved: IFluidResolvedUrl,
488
+ pendingLocalState?: IPendingContainerState,
489
+ ): Promise<Container> {
490
+ return Container.load(
491
+ this,
492
+ {
493
+ canReconnect: request.headers?.[LoaderHeader.reconnect],
494
+ clientDetailsOverride: request.headers?.[LoaderHeader.clientDetails],
495
+ resolvedUrl: resolved,
496
+ version: request.headers?.[LoaderHeader.version] ?? undefined,
497
+ loadMode: request.headers?.[LoaderHeader.loadMode],
498
+ },
499
+ pendingLocalState,
500
+ this.protocolHandlerBuilder,
501
+ );
502
+ }
498
503
  }