@fluidframework/container-loader 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.225277

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 (335) hide show
  1. package/.eslintrc.js +18 -21
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +364 -0
  4. package/README.md +152 -56
  5. package/api-extractor-esm.json +4 -0
  6. package/api-extractor-lint.json +4 -0
  7. package/api-extractor.json +2 -2
  8. package/api-report/container-loader.api.md +143 -0
  9. package/dist/{audience.js → audience.cjs} +15 -13
  10. package/dist/audience.cjs.map +1 -0
  11. package/dist/audience.d.ts +4 -6
  12. package/dist/audience.d.ts.map +1 -1
  13. package/dist/catchUpMonitor.cjs +43 -0
  14. package/dist/catchUpMonitor.cjs.map +1 -0
  15. package/dist/catchUpMonitor.d.ts +29 -0
  16. package/dist/catchUpMonitor.d.ts.map +1 -0
  17. package/dist/{connectionManager.js → connectionManager.cjs} +397 -240
  18. package/dist/connectionManager.cjs.map +1 -0
  19. package/dist/connectionManager.d.ts +23 -33
  20. package/dist/connectionManager.d.ts.map +1 -1
  21. package/dist/{connectionState.js → connectionState.cjs} +5 -7
  22. package/dist/connectionState.cjs.map +1 -0
  23. package/dist/connectionState.d.ts +3 -5
  24. package/dist/connectionState.d.ts.map +1 -1
  25. package/dist/connectionStateHandler.cjs +474 -0
  26. package/dist/connectionStateHandler.cjs.map +1 -0
  27. package/dist/connectionStateHandler.d.ts +127 -29
  28. package/dist/connectionStateHandler.d.ts.map +1 -1
  29. package/dist/container-loader-alpha.d.ts +274 -0
  30. package/dist/container-loader-beta.d.ts +75 -0
  31. package/dist/container-loader-public.d.ts +75 -0
  32. package/dist/container-loader-untrimmed.d.ts +331 -0
  33. package/dist/container.cjs +1585 -0
  34. package/dist/container.cjs.map +1 -0
  35. package/dist/container.d.ts +227 -83
  36. package/dist/container.d.ts.map +1 -1
  37. package/dist/containerContext.cjs +74 -0
  38. package/dist/containerContext.cjs.map +1 -0
  39. package/dist/containerContext.d.ts +33 -59
  40. package/dist/containerContext.d.ts.map +1 -1
  41. package/dist/containerStorageAdapter.cjs +234 -0
  42. package/dist/containerStorageAdapter.cjs.map +1 -0
  43. package/dist/containerStorageAdapter.d.ts +48 -23
  44. package/dist/containerStorageAdapter.d.ts.map +1 -1
  45. package/dist/{contracts.js → contracts.cjs} +5 -5
  46. package/dist/contracts.cjs.map +1 -0
  47. package/dist/contracts.d.ts +45 -17
  48. package/dist/contracts.d.ts.map +1 -1
  49. package/dist/debugLogger.cjs +101 -0
  50. package/dist/debugLogger.cjs.map +1 -0
  51. package/dist/debugLogger.d.ts +30 -0
  52. package/dist/debugLogger.d.ts.map +1 -0
  53. package/dist/{deltaManager.js → deltaManager.cjs} +379 -186
  54. package/dist/deltaManager.cjs.map +1 -0
  55. package/dist/deltaManager.d.ts +54 -18
  56. package/dist/deltaManager.d.ts.map +1 -1
  57. package/dist/{deltaQueue.js → deltaQueue.cjs} +29 -28
  58. package/dist/deltaQueue.cjs.map +1 -0
  59. package/dist/deltaQueue.d.ts +3 -4
  60. package/dist/deltaQueue.d.ts.map +1 -1
  61. package/dist/disposal.cjs +25 -0
  62. package/dist/disposal.cjs.map +1 -0
  63. package/dist/disposal.d.ts +13 -0
  64. package/dist/disposal.d.ts.map +1 -0
  65. package/dist/error.cjs +32 -0
  66. package/dist/error.cjs.map +1 -0
  67. package/dist/error.d.ts +23 -0
  68. package/dist/error.d.ts.map +1 -0
  69. package/dist/index.cjs +19 -0
  70. package/dist/index.cjs.map +1 -0
  71. package/dist/index.d.ts +5 -2
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/loader.cjs +148 -0
  74. package/dist/loader.cjs.map +1 -0
  75. package/dist/loader.d.ts +38 -19
  76. package/dist/loader.d.ts.map +1 -1
  77. package/dist/location-redirection-utilities/index.cjs +11 -0
  78. package/dist/location-redirection-utilities/index.cjs.map +1 -0
  79. package/dist/location-redirection-utilities/index.d.ts +6 -0
  80. package/dist/location-redirection-utilities/index.d.ts.map +1 -0
  81. package/dist/location-redirection-utilities/resolveWithLocationRedirection.cjs +53 -0
  82. package/dist/location-redirection-utilities/resolveWithLocationRedirection.cjs.map +1 -0
  83. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +24 -0
  84. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  85. package/dist/{collabWindowTracker.js → noopHeuristic.cjs} +37 -39
  86. package/dist/noopHeuristic.cjs.map +1 -0
  87. package/dist/noopHeuristic.d.ts +23 -0
  88. package/dist/noopHeuristic.d.ts.map +1 -0
  89. package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
  90. package/dist/packageVersion.cjs.map +1 -0
  91. package/dist/packageVersion.d.ts +1 -1
  92. package/dist/packageVersion.d.ts.map +1 -1
  93. package/dist/protocol.cjs +99 -0
  94. package/dist/protocol.cjs.map +1 -0
  95. package/dist/protocol.d.ts +38 -0
  96. package/dist/protocol.d.ts.map +1 -0
  97. package/dist/{protocolTreeDocumentStorageService.js → protocolTreeDocumentStorageService.cjs} +8 -5
  98. package/dist/protocolTreeDocumentStorageService.cjs.map +1 -0
  99. package/dist/protocolTreeDocumentStorageService.d.ts +8 -4
  100. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  101. package/dist/quorum.cjs +16 -0
  102. package/dist/quorum.cjs.map +1 -0
  103. package/dist/quorum.d.ts +1 -14
  104. package/dist/quorum.d.ts.map +1 -1
  105. package/dist/{retriableDocumentStorageService.js → retriableDocumentStorageService.cjs} +36 -21
  106. package/dist/retriableDocumentStorageService.cjs.map +1 -0
  107. package/dist/retriableDocumentStorageService.d.ts +7 -5
  108. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  109. package/dist/tsdoc-metadata.json +11 -0
  110. package/dist/{utils.js → utils.cjs} +52 -14
  111. package/dist/utils.cjs.map +1 -0
  112. package/dist/utils.d.ts +34 -1
  113. package/dist/utils.d.ts.map +1 -1
  114. package/lib/{audience.d.ts → audience.d.mts} +5 -11
  115. package/lib/audience.d.mts.map +1 -0
  116. package/lib/{audience.js → audience.mjs} +15 -17
  117. package/lib/audience.mjs.map +1 -0
  118. package/lib/catchUpMonitor.d.mts +29 -0
  119. package/lib/catchUpMonitor.d.mts.map +1 -0
  120. package/lib/catchUpMonitor.mjs +39 -0
  121. package/lib/catchUpMonitor.mjs.map +1 -0
  122. package/lib/{connectionManager.d.ts → connectionManager.d.mts} +24 -34
  123. package/lib/connectionManager.d.mts.map +1 -0
  124. package/lib/{connectionManager.js → connectionManager.mjs} +378 -218
  125. package/lib/connectionManager.mjs.map +1 -0
  126. package/lib/{connectionState.d.ts → connectionState.d.mts} +4 -6
  127. package/lib/connectionState.d.mts.map +1 -0
  128. package/lib/{connectionState.js → connectionState.mjs} +4 -6
  129. package/lib/connectionState.mjs.map +1 -0
  130. package/lib/connectionStateHandler.d.mts +179 -0
  131. package/lib/connectionStateHandler.d.mts.map +1 -0
  132. package/lib/connectionStateHandler.mjs +469 -0
  133. package/lib/connectionStateHandler.mjs.map +1 -0
  134. package/lib/container-loader-alpha.d.mts +274 -0
  135. package/lib/container-loader-beta.d.mts +75 -0
  136. package/lib/container-loader-public.d.mts +75 -0
  137. package/lib/container-loader-untrimmed.d.mts +331 -0
  138. package/lib/container.d.mts +382 -0
  139. package/lib/container.d.mts.map +1 -0
  140. package/lib/container.mjs +1579 -0
  141. package/lib/container.mjs.map +1 -0
  142. package/lib/containerContext.d.mts +58 -0
  143. package/lib/containerContext.d.mts.map +1 -0
  144. package/lib/containerContext.mjs +70 -0
  145. package/lib/containerContext.mjs.map +1 -0
  146. package/lib/containerStorageAdapter.d.mts +73 -0
  147. package/lib/containerStorageAdapter.d.mts.map +1 -0
  148. package/lib/containerStorageAdapter.mjs +228 -0
  149. package/lib/containerStorageAdapter.mjs.map +1 -0
  150. package/lib/{contracts.d.ts → contracts.d.mts} +46 -18
  151. package/lib/contracts.d.mts.map +1 -0
  152. package/lib/{contracts.js → contracts.mjs} +4 -4
  153. package/lib/contracts.mjs.map +1 -0
  154. package/lib/debugLogger.d.mts +30 -0
  155. package/lib/debugLogger.d.mts.map +1 -0
  156. package/lib/debugLogger.mjs +93 -0
  157. package/lib/debugLogger.mjs.map +1 -0
  158. package/lib/{deltaManager.d.ts → deltaManager.d.mts} +55 -19
  159. package/lib/deltaManager.d.mts.map +1 -0
  160. package/lib/{deltaManager.js → deltaManager.mjs} +361 -165
  161. package/lib/deltaManager.mjs.map +1 -0
  162. package/lib/{deltaQueue.d.ts → deltaQueue.d.mts} +4 -5
  163. package/lib/deltaQueue.d.mts.map +1 -0
  164. package/lib/{deltaQueue.js → deltaQueue.mjs} +25 -24
  165. package/lib/deltaQueue.mjs.map +1 -0
  166. package/lib/disposal.d.mts +13 -0
  167. package/lib/disposal.d.mts.map +1 -0
  168. package/lib/disposal.mjs +21 -0
  169. package/lib/disposal.mjs.map +1 -0
  170. package/lib/error.d.mts +23 -0
  171. package/lib/error.d.mts.map +1 -0
  172. package/lib/error.mjs +28 -0
  173. package/lib/error.mjs.map +1 -0
  174. package/lib/index.d.mts +11 -0
  175. package/lib/index.d.mts.map +1 -0
  176. package/lib/index.mjs +10 -0
  177. package/lib/index.mjs.map +1 -0
  178. package/lib/{loader.d.ts → loader.d.mts} +40 -21
  179. package/lib/loader.d.mts.map +1 -0
  180. package/lib/loader.mjs +143 -0
  181. package/lib/loader.mjs.map +1 -0
  182. package/lib/location-redirection-utilities/index.d.mts +6 -0
  183. package/lib/location-redirection-utilities/index.d.mts.map +1 -0
  184. package/lib/location-redirection-utilities/index.mjs +6 -0
  185. package/lib/location-redirection-utilities/index.mjs.map +1 -0
  186. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.mts +24 -0
  187. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.mts.map +1 -0
  188. package/lib/location-redirection-utilities/resolveWithLocationRedirection.mjs +48 -0
  189. package/lib/location-redirection-utilities/resolveWithLocationRedirection.mjs.map +1 -0
  190. package/lib/noopHeuristic.d.mts +23 -0
  191. package/lib/noopHeuristic.d.mts.map +1 -0
  192. package/lib/{collabWindowTracker.js → noopHeuristic.mjs} +33 -35
  193. package/lib/noopHeuristic.mjs.map +1 -0
  194. package/lib/{packageVersion.d.ts → packageVersion.d.mts} +2 -2
  195. package/lib/packageVersion.d.mts.map +1 -0
  196. package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
  197. package/lib/packageVersion.mjs.map +1 -0
  198. package/lib/protocol.d.mts +38 -0
  199. package/lib/protocol.d.mts.map +1 -0
  200. package/lib/protocol.mjs +94 -0
  201. package/lib/protocol.mjs.map +1 -0
  202. package/lib/{protocolTreeDocumentStorageService.d.ts → protocolTreeDocumentStorageService.d.mts} +9 -5
  203. package/lib/protocolTreeDocumentStorageService.d.mts.map +1 -0
  204. package/lib/{protocolTreeDocumentStorageService.js → protocolTreeDocumentStorageService.mjs} +8 -5
  205. package/lib/protocolTreeDocumentStorageService.mjs.map +1 -0
  206. package/lib/quorum.d.mts +4 -0
  207. package/lib/quorum.d.mts.map +1 -0
  208. package/lib/quorum.mjs +12 -0
  209. package/lib/quorum.mjs.map +1 -0
  210. package/lib/{retriableDocumentStorageService.d.ts → retriableDocumentStorageService.d.mts} +8 -6
  211. package/lib/retriableDocumentStorageService.d.mts.map +1 -0
  212. package/lib/{retriableDocumentStorageService.js → retriableDocumentStorageService.mjs} +35 -20
  213. package/lib/retriableDocumentStorageService.mjs.map +1 -0
  214. package/lib/utils.d.mts +67 -0
  215. package/lib/utils.d.mts.map +1 -0
  216. package/lib/{utils.js → utils.mjs} +47 -11
  217. package/lib/utils.mjs.map +1 -0
  218. package/package.json +189 -69
  219. package/prettier.config.cjs +8 -0
  220. package/src/audience.ts +59 -49
  221. package/src/catchUpMonitor.ts +61 -0
  222. package/src/connectionManager.ts +1154 -910
  223. package/src/connectionState.ts +22 -25
  224. package/src/connectionStateHandler.ts +689 -319
  225. package/src/container.ts +2476 -1792
  226. package/src/containerContext.ts +98 -330
  227. package/src/containerStorageAdapter.ts +301 -105
  228. package/src/contracts.ts +184 -146
  229. package/src/debugLogger.ts +123 -0
  230. package/src/deltaManager.ts +1165 -900
  231. package/src/deltaQueue.ts +156 -152
  232. package/src/disposal.ts +25 -0
  233. package/src/error.ts +44 -0
  234. package/src/index.ts +14 -15
  235. package/src/loader.ts +356 -427
  236. package/src/location-redirection-utilities/index.ts +9 -0
  237. package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +61 -0
  238. package/src/noopHeuristic.ts +107 -0
  239. package/src/packageVersion.ts +1 -1
  240. package/src/protocol.ts +150 -0
  241. package/src/protocolTreeDocumentStorageService.ts +35 -35
  242. package/src/quorum.ts +11 -50
  243. package/src/retriableDocumentStorageService.ts +135 -95
  244. package/src/utils.ts +159 -86
  245. package/tsc-multi.test.json +4 -0
  246. package/tsconfig.json +10 -12
  247. package/dist/audience.js.map +0 -1
  248. package/dist/collabWindowTracker.d.ts +0 -19
  249. package/dist/collabWindowTracker.d.ts.map +0 -1
  250. package/dist/collabWindowTracker.js.map +0 -1
  251. package/dist/connectionManager.js.map +0 -1
  252. package/dist/connectionState.js.map +0 -1
  253. package/dist/connectionStateHandler.js +0 -280
  254. package/dist/connectionStateHandler.js.map +0 -1
  255. package/dist/container.js +0 -1284
  256. package/dist/container.js.map +0 -1
  257. package/dist/containerContext.js +0 -217
  258. package/dist/containerContext.js.map +0 -1
  259. package/dist/containerStorageAdapter.js +0 -104
  260. package/dist/containerStorageAdapter.js.map +0 -1
  261. package/dist/contracts.js.map +0 -1
  262. package/dist/deltaManager.js.map +0 -1
  263. package/dist/deltaManagerProxy.d.ts +0 -54
  264. package/dist/deltaManagerProxy.d.ts.map +0 -1
  265. package/dist/deltaManagerProxy.js +0 -115
  266. package/dist/deltaManagerProxy.js.map +0 -1
  267. package/dist/deltaQueue.js.map +0 -1
  268. package/dist/index.js +0 -16
  269. package/dist/index.js.map +0 -1
  270. package/dist/loader.js +0 -241
  271. package/dist/loader.js.map +0 -1
  272. package/dist/packageVersion.js.map +0 -1
  273. package/dist/protocolTreeDocumentStorageService.js.map +0 -1
  274. package/dist/quorum.js +0 -44
  275. package/dist/quorum.js.map +0 -1
  276. package/dist/retriableDocumentStorageService.js.map +0 -1
  277. package/dist/utils.js.map +0 -1
  278. package/lib/audience.d.ts.map +0 -1
  279. package/lib/audience.js.map +0 -1
  280. package/lib/collabWindowTracker.d.ts +0 -19
  281. package/lib/collabWindowTracker.d.ts.map +0 -1
  282. package/lib/collabWindowTracker.js.map +0 -1
  283. package/lib/connectionManager.d.ts.map +0 -1
  284. package/lib/connectionManager.js.map +0 -1
  285. package/lib/connectionState.d.ts.map +0 -1
  286. package/lib/connectionState.js.map +0 -1
  287. package/lib/connectionStateHandler.d.ts +0 -81
  288. package/lib/connectionStateHandler.d.ts.map +0 -1
  289. package/lib/connectionStateHandler.js +0 -276
  290. package/lib/connectionStateHandler.js.map +0 -1
  291. package/lib/container.d.ts +0 -238
  292. package/lib/container.d.ts.map +0 -1
  293. package/lib/container.js +0 -1276
  294. package/lib/container.js.map +0 -1
  295. package/lib/containerContext.d.ts +0 -84
  296. package/lib/containerContext.d.ts.map +0 -1
  297. package/lib/containerContext.js +0 -213
  298. package/lib/containerContext.js.map +0 -1
  299. package/lib/containerStorageAdapter.d.ts +0 -48
  300. package/lib/containerStorageAdapter.d.ts.map +0 -1
  301. package/lib/containerStorageAdapter.js +0 -99
  302. package/lib/containerStorageAdapter.js.map +0 -1
  303. package/lib/contracts.d.ts.map +0 -1
  304. package/lib/contracts.js.map +0 -1
  305. package/lib/deltaManager.d.ts.map +0 -1
  306. package/lib/deltaManager.js.map +0 -1
  307. package/lib/deltaManagerProxy.d.ts +0 -54
  308. package/lib/deltaManagerProxy.d.ts.map +0 -1
  309. package/lib/deltaManagerProxy.js +0 -110
  310. package/lib/deltaManagerProxy.js.map +0 -1
  311. package/lib/deltaQueue.d.ts.map +0 -1
  312. package/lib/deltaQueue.js.map +0 -1
  313. package/lib/index.d.ts +0 -8
  314. package/lib/index.d.ts.map +0 -1
  315. package/lib/index.js +0 -8
  316. package/lib/index.js.map +0 -1
  317. package/lib/loader.d.ts.map +0 -1
  318. package/lib/loader.js +0 -236
  319. package/lib/loader.js.map +0 -1
  320. package/lib/packageVersion.d.ts.map +0 -1
  321. package/lib/packageVersion.js.map +0 -1
  322. package/lib/protocolTreeDocumentStorageService.d.ts.map +0 -1
  323. package/lib/protocolTreeDocumentStorageService.js.map +0 -1
  324. package/lib/quorum.d.ts +0 -21
  325. package/lib/quorum.d.ts.map +0 -1
  326. package/lib/quorum.js +0 -38
  327. package/lib/quorum.js.map +0 -1
  328. package/lib/retriableDocumentStorageService.d.ts.map +0 -1
  329. package/lib/retriableDocumentStorageService.js.map +0 -1
  330. package/lib/utils.d.ts +0 -34
  331. package/lib/utils.d.ts.map +0 -1
  332. package/lib/utils.js.map +0 -1
  333. package/src/collabWindowTracker.ts +0 -102
  334. package/src/deltaManagerProxy.ts +0 -158
  335. package/tsconfig.esnext.json +0 -7
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export {
7
+ isLocationRedirectionError,
8
+ resolveWithLocationRedirectionHandling,
9
+ } from "./resolveWithLocationRedirection";
@@ -0,0 +1,61 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ITelemetryBaseLogger, IRequest } from "@fluidframework/core-interfaces";
7
+ import {
8
+ DriverErrorTypes,
9
+ ILocationRedirectionError,
10
+ IUrlResolver,
11
+ } from "@fluidframework/driver-definitions";
12
+ import { createChildLogger } from "@fluidframework/telemetry-utils";
13
+
14
+ /**
15
+ * Checks if the error is location redirection error.
16
+ * @param error - error whose type is to be determined.
17
+ * @returns `true` is the error is location redirection error, otherwise `false`.
18
+ * @internal
19
+ */
20
+ export function isLocationRedirectionError(error: any): error is ILocationRedirectionError {
21
+ return (
22
+ typeof error === "object" &&
23
+ error !== null &&
24
+ error.errorType === DriverErrorTypes.locationRedirection
25
+ );
26
+ }
27
+
28
+ /**
29
+ * Handles location redirection while fulfilling the loader request.
30
+ * @param api - Callback in which user can wrap the loader.resolve or loader.request call.
31
+ * @param request - request to be resolved.
32
+ * @param urlResolver - resolver used to resolve the url.
33
+ * @param logger - logger to send events.
34
+ * @returns Response from the API call.
35
+ * @alpha
36
+ */
37
+ export async function resolveWithLocationRedirectionHandling<T>(
38
+ api: (request: IRequest) => Promise<T>,
39
+ request: IRequest,
40
+ urlResolver: IUrlResolver,
41
+ logger?: ITelemetryBaseLogger,
42
+ ): Promise<T> {
43
+ let req: IRequest = request;
44
+ const childLogger = createChildLogger({ logger, namespace: "LocationRedirection" });
45
+ for (;;) {
46
+ try {
47
+ return await api(req);
48
+ } catch (error: any) {
49
+ if (!isLocationRedirectionError(error)) {
50
+ throw error;
51
+ }
52
+ childLogger.sendTelemetryEvent({ eventName: "LocationRedirectionError" });
53
+ const resolvedUrl = error.redirectUrl;
54
+ // Generate the new request with new location details from the resolved url. For datastore/relative path,
55
+ // we don't need to pass "/" as host could have asked for a specific data store. So driver need to
56
+ // extract it from the resolved url.
57
+ const absoluteUrl = await urlResolver.getAbsoluteUrl(resolvedUrl, "", undefined);
58
+ req = { url: absoluteUrl, headers: req.headers };
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,107 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { TypedEventEmitter } from "@fluid-internal/client-utils";
7
+ import { assert, Timer } from "@fluidframework/core-utils";
8
+ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
9
+ import { isRuntimeMessage } from "@fluidframework/driver-utils";
10
+ import { IEvent } from "@fluidframework/core-interfaces";
11
+
12
+ const defaultNoopTimeFrequency = 2000;
13
+ const defaultNoopCountFrequency = 50;
14
+
15
+ export interface INoopSenderEvents extends IEvent {
16
+ (event: "wantsNoop", listener: () => void);
17
+ }
18
+
19
+ // Here are key considerations when deciding conditions for when to send non-immediate noops:
20
+ // 1. Sending them too often results in increase in file size and bandwidth, as well as catch up performance
21
+ // 2. Sending too infrequently ensures that collab window is large, and as result Sequence DDS would have
22
+ // large catchUp blobs - see Issue #6364
23
+ // 3. Similarly, processes that rely on "core" snapshot (and can't parse trailing ops, including above), like search
24
+ // parser in SPO, will result in non-accurate results due to presence of catch up blobs.
25
+ // 4. Ordering service used 250ms timeout to coalesce non-immediate noops. It was changed to 2000 ms to allow more
26
+ // aggressive noop sending from client side.
27
+ // 5. Number of ops sent by all clients is proportional to number of "write" clients (every client sends noops),
28
+ // but number of sequenced noops is a function of time (one op per 2 seconds at most).
29
+ // We should consider impact to both outbound traffic (might be huge, depends on number of clients) and file size.
30
+ // Please also see Issue #5629 for more discussions.
31
+ //
32
+ // With that, the current algorithm is as follows:
33
+ // 1. Sent noop 2000 ms of receiving an op if no ops were sent by this client within this timeframe.
34
+ // This will ensure that MSN moves forward with reasonable speed. If that results in too many sequenced noops,
35
+ // server timeout of 2000ms should be reconsidered to be increased.
36
+ // 2. If there are more than 50 ops received without sending any ops, send noop to keep collab window small.
37
+ // Note that system ops (including noops themselves) are excluded, so it's 1 noop per 50 real ops.
38
+ export class NoopHeuristic extends TypedEventEmitter<INoopSenderEvents> {
39
+ private opsProcessedSinceOpSent = 0;
40
+ private readonly timer: Timer | undefined;
41
+
42
+ constructor(
43
+ NoopTimeFrequency: number = defaultNoopTimeFrequency,
44
+ private readonly NoopCountFrequency: number = defaultNoopCountFrequency,
45
+ ) {
46
+ super();
47
+ if (NoopTimeFrequency !== Infinity) {
48
+ this.timer = new Timer(NoopTimeFrequency, () => {
49
+ // We allow the timer to expire even if an op is sent or we disconnect.
50
+ // This condition is to guard against trying to send a noop anyway in that case.
51
+ if (this.opsProcessedSinceOpSent !== 0) {
52
+ this.emit("wantsNoop");
53
+ }
54
+ });
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Schedules as ack to the server to update the reference sequence number
60
+ */
61
+ public notifyMessageProcessed(message: ISequencedDocumentMessage): void {
62
+ // We don't acknowledge no-ops to avoid acknowledgement cycles (i.e. ack the MSN
63
+ // update, which updates the MSN, then ack the update, etc...).
64
+ // Intent here is for runtime (and DDSes) not to keep too much tracking state / memory
65
+ // due to runtime ops from other clients.
66
+ if (!isRuntimeMessage(message)) {
67
+ return;
68
+ }
69
+
70
+ this.opsProcessedSinceOpSent++;
71
+ if (this.opsProcessedSinceOpSent === this.NoopCountFrequency) {
72
+ // Wait to send a noop if we are still synchronously processing ops. This guards against two things:
73
+ // 1. If we're processing many ops, we may pass the frequency threshold many times. We only need to send one noop at the very end in this case.
74
+ // 2. We may send another (non-noop) op in response to processing those ops, e.g. an Accept op.
75
+ queueMicrotask(() => {
76
+ if (this.opsProcessedSinceOpSent >= this.NoopCountFrequency) {
77
+ this.emit("wantsNoop");
78
+ assert(
79
+ this.opsProcessedSinceOpSent === 0,
80
+ 0x243 /* "Expected a noop to be synchronously sent" */,
81
+ );
82
+ }
83
+ return;
84
+ });
85
+ }
86
+
87
+ if (this.timer !== undefined) {
88
+ // Start the timer if we newly have ops that want a noop.
89
+ // If the timer was already running (e.g. we surpassed the op count and sent a noop) this will reset it to its full duration.
90
+ if (this.opsProcessedSinceOpSent === 1) {
91
+ this.timer.restart();
92
+ }
93
+
94
+ assert(this.timer.hasTimer, 0x242 /* "has timer" */);
95
+ }
96
+ }
97
+
98
+ public notifyDisconnect(): void {
99
+ // No need to noop for any ops processed prior to disconnect - we are already removed from MSN calculation.
100
+ this.opsProcessedSinceOpSent = 0;
101
+ }
102
+
103
+ public notifyMessageSent(): void {
104
+ // Sending any message is as good as a noop for updating MSN.
105
+ this.opsProcessedSinceOpSent = 0;
106
+ }
107
+ }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "1.4.0-121020";
9
+ export const pkgVersion = "2.0.0-dev-rc.1.0.0.225277";
@@ -0,0 +1,150 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { IAudienceOwner } from "@fluidframework/container-definitions";
7
+ import { canBeCoalescedByService } from "@fluidframework/driver-utils";
8
+ import {
9
+ IProtocolHandler as IBaseProtocolHandler,
10
+ IQuorumSnapshot,
11
+ ProtocolOpHandler,
12
+ } from "@fluidframework/protocol-base";
13
+ import {
14
+ IDocumentAttributes,
15
+ IProcessMessageResult,
16
+ ISequencedDocumentMessage,
17
+ ISignalClient,
18
+ ISignalMessage,
19
+ MessageType,
20
+ } from "@fluidframework/protocol-definitions";
21
+
22
+ // ADO: #1986: Start using enum from protocol-base.
23
+ export enum SignalType {
24
+ ClientJoin = "join", // same value as MessageType.ClientJoin,
25
+ ClientLeave = "leave", // same value as MessageType.ClientLeave,
26
+ Clear = "clear", // used only by client for synthetic signals
27
+ }
28
+
29
+ /**
30
+ * Function to be used for creating a protocol handler.
31
+ * @alpha
32
+ */
33
+ export type ProtocolHandlerBuilder = (
34
+ attributes: IDocumentAttributes,
35
+ snapshot: IQuorumSnapshot,
36
+ sendProposal: (key: string, value: any) => number,
37
+ ) => IProtocolHandler;
38
+
39
+ /**
40
+ * @alpha
41
+ */
42
+ export interface IProtocolHandler extends IBaseProtocolHandler {
43
+ readonly audience: IAudienceOwner;
44
+ processSignal(message: ISignalMessage);
45
+ }
46
+
47
+ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {
48
+ constructor(
49
+ attributes: IDocumentAttributes,
50
+ quorumSnapshot: IQuorumSnapshot,
51
+ sendProposal: (key: string, value: any) => number,
52
+ public readonly audience: IAudienceOwner,
53
+ private readonly shouldClientHaveLeft: (clientId: string) => boolean,
54
+ ) {
55
+ super(
56
+ attributes.minimumSequenceNumber,
57
+ attributes.sequenceNumber,
58
+ quorumSnapshot.members,
59
+ quorumSnapshot.proposals,
60
+ quorumSnapshot.values,
61
+ sendProposal,
62
+ );
63
+
64
+ // Join / leave signals are ignored for "write" clients in favor of join / leave ops
65
+ this.quorum.on("addMember", (clientId, details) =>
66
+ audience.addMember(clientId, details.client),
67
+ );
68
+ this.quorum.on("removeMember", (clientId) => audience.removeMember(clientId));
69
+ for (const [clientId, details] of this.quorum.getMembers()) {
70
+ this.audience.addMember(clientId, details.client);
71
+ }
72
+ }
73
+
74
+ public processMessage(
75
+ message: ISequencedDocumentMessage,
76
+ local: boolean,
77
+ ): IProcessMessageResult {
78
+ // Check and report if we're getting messages from a clientId that we previously
79
+ // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
80
+ if (message.clientId != null) {
81
+ const client = this.quorum.getMember(message.clientId);
82
+
83
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
84
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
85
+ throw new Error("Remote message's clientId is missing from the quorum");
86
+ }
87
+
88
+ // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
89
+ // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
90
+ // document we don't need to blow up aggressively.
91
+ if (this.shouldClientHaveLeft(message.clientId) && !canBeCoalescedByService(message)) {
92
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
93
+ throw new Error("Remote message's clientId already should have left");
94
+ }
95
+ }
96
+
97
+ return super.processMessage(message, local);
98
+ }
99
+
100
+ public processSignal(message: ISignalMessage) {
101
+ const innerContent = message.content as { content: any; type: string };
102
+ switch (innerContent.type) {
103
+ case SignalType.Clear: {
104
+ const members = this.audience.getMembers();
105
+ for (const [clientId, client] of members) {
106
+ if (client.mode === "read") {
107
+ this.audience.removeMember(clientId);
108
+ }
109
+ }
110
+ break;
111
+ }
112
+ case SignalType.ClientJoin: {
113
+ const newClient = innerContent.content as ISignalClient;
114
+ // Ignore write clients - quorum will control such clients.
115
+ if (newClient.client.mode === "read") {
116
+ this.audience.addMember(newClient.clientId, newClient.client);
117
+ }
118
+ break;
119
+ }
120
+ case SignalType.ClientLeave: {
121
+ const leftClientId = innerContent.content as string;
122
+ // Ignore write clients - quorum will control such clients.
123
+ if (this.audience.getMember(leftClientId)?.mode === "read") {
124
+ this.audience.removeMember(leftClientId);
125
+ }
126
+ break;
127
+ }
128
+ default:
129
+ break;
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Function to check whether the protocol handler should process the Signal.
136
+ * The protocol handler should strictly handle only ClientJoin, ClientLeave
137
+ * and Clear signal types.
138
+ */
139
+ export function protocolHandlerShouldProcessSignal(message: ISignalMessage) {
140
+ // Signal originates from server
141
+ if (message.clientId === null) {
142
+ const innerContent = message.content as { content: unknown; type: string };
143
+ return (
144
+ innerContent.type === SignalType.Clear ||
145
+ innerContent.type === SignalType.ClientJoin ||
146
+ innerContent.type === SignalType.ClientLeave
147
+ );
148
+ }
149
+ return false;
150
+ }
@@ -3,43 +3,43 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable } from "@fluidframework/common-definitions";
7
- import {
8
- IDocumentStorageService,
9
- ISummaryContext,
10
- } from "@fluidframework/driver-definitions";
11
- import { combineAppAndProtocolSummary } from "@fluidframework/driver-utils";
12
- import {
13
- ISummaryTree,
14
- } from "@fluidframework/protocol-definitions";
6
+ import { IDisposable } from "@fluidframework/core-interfaces";
7
+ import { IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
8
+ import { ISummaryTree } from "@fluidframework/protocol-definitions";
15
9
 
10
+ /**
11
+ * A storage service wrapper whose sole job is to intercept calls to uploadSummaryWithContext and ensure they include
12
+ * the protocol summary, using the provided callback to add it if necessary.
13
+ */
16
14
  export class ProtocolTreeStorageService implements IDocumentStorageService, IDisposable {
17
- constructor(
18
- private readonly internalStorageService: IDocumentStorageService & IDisposable,
19
- private readonly generateProtocolTree: () => ISummaryTree,
20
- ) {
21
- }
22
- public get policies() {
23
- return this.internalStorageService.policies;
24
- }
25
- public get repositoryUrl() {
26
- return this.internalStorageService.repositoryUrl;
27
- }
28
- public get disposed() {
29
- return this.internalStorageService.disposed;
30
- }
15
+ constructor(
16
+ private readonly internalStorageService: IDocumentStorageService & IDisposable,
17
+ private readonly addProtocolSummaryIfMissing: (summaryTree: ISummaryTree) => ISummaryTree,
18
+ ) {}
19
+ public get policies() {
20
+ return this.internalStorageService.policies;
21
+ }
22
+ public get repositoryUrl() {
23
+ return this.internalStorageService.repositoryUrl;
24
+ }
25
+ public get disposed() {
26
+ return this.internalStorageService.disposed;
27
+ }
31
28
 
32
- getSnapshotTree = this.internalStorageService.getSnapshotTree.bind(this.internalStorageService);
33
- getVersions = this.internalStorageService.getVersions.bind(this.internalStorageService);
34
- createBlob = this.internalStorageService.createBlob.bind(this.internalStorageService);
35
- readBlob = this.internalStorageService.readBlob.bind(this.internalStorageService);
36
- downloadSummary = this.internalStorageService.downloadSummary.bind(this.internalStorageService);
37
- dispose = this.internalStorageService.dispose.bind(this.internalStorageService);
29
+ getSnapshotTree = this.internalStorageService.getSnapshotTree.bind(this.internalStorageService);
30
+ getVersions = this.internalStorageService.getVersions.bind(this.internalStorageService);
31
+ createBlob = this.internalStorageService.createBlob.bind(this.internalStorageService);
32
+ readBlob = this.internalStorageService.readBlob.bind(this.internalStorageService);
33
+ downloadSummary = this.internalStorageService.downloadSummary.bind(this.internalStorageService);
34
+ dispose = this.internalStorageService.dispose.bind(this.internalStorageService);
38
35
 
39
- async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {
40
- return this.internalStorageService.uploadSummaryWithContext(
41
- combineAppAndProtocolSummary(summary, this.generateProtocolTree()),
42
- context,
43
- );
44
- }
36
+ async uploadSummaryWithContext(
37
+ summary: ISummaryTree,
38
+ context: ISummaryContext,
39
+ ): Promise<string> {
40
+ return this.internalStorageService.uploadSummaryWithContext(
41
+ this.addProtocolSummaryIfMissing(summary),
42
+ context,
43
+ );
44
+ }
45
45
  }
package/src/quorum.ts CHANGED
@@ -2,58 +2,19 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { assert, EventForwarder, doIfNotDisposed } from "@fluidframework/common-utils";
6
5
  import { IFluidCodeDetails } from "@fluidframework/core-interfaces";
7
- import {
8
- ICommittedProposal,
9
- IQuorum,
10
- IQuorumEvents,
11
- ISequencedClient,
12
- } from "@fluidframework/protocol-definitions";
13
-
14
- /**
15
- * Proxies Quorum events.
16
- */
17
- export class QuorumProxy extends EventForwarder<IQuorumEvents> implements IQuorum {
18
- public readonly propose: (key: string, value: any) => Promise<void>;
19
- public readonly has: (key: string) => boolean;
20
- public readonly get: (key: string) => any;
21
- public readonly getMembers: () => Map<string, ISequencedClient>;
22
- public readonly getMember: (clientId: string) => ISequencedClient | undefined;
23
-
24
- constructor(quorum: IQuorum) {
25
- super(quorum);
26
-
27
- // This is heavily used object, increase limit at which Node prints warnings.
28
- super.setMaxListeners(50);
29
-
30
- this.propose = doIfNotDisposed(this, quorum.propose.bind(quorum));
31
- this.has = doIfNotDisposed(this, quorum.has.bind(quorum));
32
- this.get = doIfNotDisposed(this, quorum.get.bind(quorum));
33
- this.getMembers = doIfNotDisposed(this, quorum.getMembers.bind(quorum));
34
- this.getMember = doIfNotDisposed(this, quorum.getMember.bind(quorum));
35
- }
36
- }
37
-
38
- export function getCodeDetailsFromQuorumValues(
39
- quorumValues: [string, ICommittedProposal][],
40
- ): IFluidCodeDetails {
41
- const qValuesMap = new Map(quorumValues);
42
- const proposal = qValuesMap.get("code");
43
- assert(proposal !== undefined, 0x2dc /* "Cannot find code proposal" */);
44
- return proposal?.value as IFluidCodeDetails;
45
- }
6
+ import { ICommittedProposal } from "@fluidframework/protocol-definitions";
46
7
 
47
8
  export function initQuorumValuesFromCodeDetails(
48
- source: IFluidCodeDetails,
9
+ source: IFluidCodeDetails,
49
10
  ): [string, ICommittedProposal][] {
50
- // Seed the base quorum to be an empty list with a code quorum set
51
- const committedCodeProposal: ICommittedProposal = {
52
- key: "code",
53
- value: source,
54
- approvalSequenceNumber: 0,
55
- commitSequenceNumber: 0,
56
- sequenceNumber: 0,
57
- };
58
- return [["code", committedCodeProposal]];
11
+ // Seed the base quorum to be an empty list with a code quorum set
12
+ const committedCodeProposal: ICommittedProposal = {
13
+ key: "code",
14
+ value: source,
15
+ approvalSequenceNumber: 0,
16
+ commitSequenceNumber: 0,
17
+ sequenceNumber: 0,
18
+ };
19
+ return [["code", committedCodeProposal]];
59
20
  }