@fluidframework/container-loader 2.117.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/CHANGELOG.md +76 -2
  2. package/README.md +53 -50
  3. package/{api-extractor.json → api-extractor/api-extractor-model.json} +1 -1
  4. package/api-report/container-loader.legacy.alpha.api.md +4 -3
  5. package/dist/attachment.d.ts.map +1 -1
  6. package/dist/audience.js +3 -2
  7. package/dist/audience.js.map +1 -1
  8. package/dist/captureReferencedContents.js +9 -9
  9. package/dist/captureReferencedContents.js.map +1 -1
  10. package/dist/catchUpMonitor.js +11 -8
  11. package/dist/catchUpMonitor.js.map +1 -1
  12. package/dist/connectionManager.d.ts.map +1 -1
  13. package/dist/connectionManager.js +86 -51
  14. package/dist/connectionManager.js.map +1 -1
  15. package/dist/connectionStateHandler.js +40 -14
  16. package/dist/connectionStateHandler.js.map +1 -1
  17. package/dist/container.d.ts +1 -1
  18. package/dist/container.d.ts.map +1 -1
  19. package/dist/container.js +193 -143
  20. package/dist/container.js.map +1 -1
  21. package/dist/containerContext.js +48 -11
  22. package/dist/containerContext.js.map +1 -1
  23. package/dist/containerStorageAdapter.js +25 -17
  24. package/dist/containerStorageAdapter.js.map +1 -1
  25. package/dist/contracts.d.ts.map +1 -1
  26. package/dist/createAndLoadContainerUtils.d.ts +34 -13
  27. package/dist/createAndLoadContainerUtils.d.ts.map +1 -1
  28. package/dist/createAndLoadContainerUtils.js +97 -37
  29. package/dist/createAndLoadContainerUtils.js.map +1 -1
  30. package/dist/debugLogger.d.ts +7 -22
  31. package/dist/debugLogger.d.ts.map +1 -1
  32. package/dist/debugLogger.js +34 -34
  33. package/dist/debugLogger.js.map +1 -1
  34. package/dist/deltaManager.d.ts +16 -4
  35. package/dist/deltaManager.d.ts.map +1 -1
  36. package/dist/deltaManager.js +75 -49
  37. package/dist/deltaManager.js.map +1 -1
  38. package/dist/deltaManagerFactory.d.ts +23 -0
  39. package/dist/deltaManagerFactory.d.ts.map +1 -0
  40. package/dist/deltaManagerFactory.js +17 -0
  41. package/dist/deltaManagerFactory.js.map +1 -0
  42. package/dist/deltaQueue.js +14 -7
  43. package/dist/deltaQueue.js.map +1 -1
  44. package/dist/disposal.js +1 -2
  45. package/dist/disposal.js.map +1 -1
  46. package/dist/error.js +5 -4
  47. package/dist/error.js.map +1 -1
  48. package/dist/frozenServices.d.ts +2 -2
  49. package/dist/frozenServices.d.ts.map +1 -1
  50. package/dist/frozenServices.js +52 -34
  51. package/dist/frozenServices.js.map +1 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +2 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/loadContainerToSequenceNumber.d.ts +6 -5
  57. package/dist/loadContainerToSequenceNumber.d.ts.map +1 -1
  58. package/dist/loadContainerToSequenceNumber.js +5 -6
  59. package/dist/loadContainerToSequenceNumber.js.map +1 -1
  60. package/dist/loadPaused.js +1 -2
  61. package/dist/loadPaused.js.map +1 -1
  62. package/dist/loader.js +5 -1
  63. package/dist/loader.js.map +1 -1
  64. package/dist/loaderLayerCompatState.d.ts +25 -1
  65. package/dist/loaderLayerCompatState.d.ts.map +1 -1
  66. package/dist/loaderLayerCompatState.js +79 -11
  67. package/dist/loaderLayerCompatState.js.map +1 -1
  68. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +2 -3
  69. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -1
  70. package/dist/memoryBlobStorage.js +2 -3
  71. package/dist/memoryBlobStorage.js.map +1 -1
  72. package/dist/noopHeuristic.js +3 -1
  73. package/dist/noopHeuristic.js.map +1 -1
  74. package/dist/packageVersion.d.ts +1 -1
  75. package/dist/packageVersion.d.ts.map +1 -1
  76. package/dist/packageVersion.js +1 -1
  77. package/dist/packageVersion.js.map +1 -1
  78. package/dist/pendingLocalStateStore.d.ts +4 -3
  79. package/dist/pendingLocalStateStore.d.ts.map +1 -1
  80. package/dist/pendingLocalStateStore.js +26 -37
  81. package/dist/pendingLocalStateStore.js.map +1 -1
  82. package/dist/pointInTimeServices.js +4 -2
  83. package/dist/pointInTimeServices.js.map +1 -1
  84. package/dist/protocol/protocol.js +3 -0
  85. package/dist/protocol/protocol.js.map +1 -1
  86. package/dist/protocol/quorum.js +26 -5
  87. package/dist/protocol/quorum.js.map +1 -1
  88. package/dist/protocol.js +5 -3
  89. package/dist/protocol.js.map +1 -1
  90. package/dist/protocolTreeDocumentStorageService.js +10 -0
  91. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  92. package/dist/quorum.js +1 -2
  93. package/dist/quorum.js.map +1 -1
  94. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  95. package/dist/retriableDocumentStorageService.js +5 -1
  96. package/dist/retriableDocumentStorageService.js.map +1 -1
  97. package/dist/serializedStateManager.d.ts +6 -0
  98. package/dist/serializedStateManager.d.ts.map +1 -1
  99. package/dist/serializedStateManager.js +19 -24
  100. package/dist/serializedStateManager.js.map +1 -1
  101. package/dist/snapshotRefresher.js +31 -36
  102. package/dist/snapshotRefresher.js.map +1 -1
  103. package/dist/utils.d.ts +1 -1
  104. package/dist/utils.d.ts.map +1 -1
  105. package/dist/utils.js +13 -13
  106. package/dist/utils.js.map +1 -1
  107. package/lib/attachment.d.ts.map +1 -1
  108. package/lib/audience.js +3 -2
  109. package/lib/audience.js.map +1 -1
  110. package/lib/catchUpMonitor.js +11 -8
  111. package/lib/catchUpMonitor.js.map +1 -1
  112. package/lib/connectionManager.d.ts.map +1 -1
  113. package/lib/connectionManager.js +86 -51
  114. package/lib/connectionManager.js.map +1 -1
  115. package/lib/connectionStateHandler.js +37 -11
  116. package/lib/connectionStateHandler.js.map +1 -1
  117. package/lib/container.d.ts +1 -1
  118. package/lib/container.d.ts.map +1 -1
  119. package/lib/container.js +191 -141
  120. package/lib/container.js.map +1 -1
  121. package/lib/containerContext.js +48 -11
  122. package/lib/containerContext.js.map +1 -1
  123. package/lib/containerStorageAdapter.js +22 -14
  124. package/lib/containerStorageAdapter.js.map +1 -1
  125. package/lib/contracts.d.ts.map +1 -1
  126. package/lib/createAndLoadContainerUtils.d.ts +34 -13
  127. package/lib/createAndLoadContainerUtils.d.ts.map +1 -1
  128. package/lib/createAndLoadContainerUtils.js +93 -32
  129. package/lib/createAndLoadContainerUtils.js.map +1 -1
  130. package/lib/debugLogger.d.ts +7 -22
  131. package/lib/debugLogger.d.ts.map +1 -1
  132. package/lib/debugLogger.js +34 -33
  133. package/lib/debugLogger.js.map +1 -1
  134. package/lib/deltaManager.d.ts +16 -4
  135. package/lib/deltaManager.d.ts.map +1 -1
  136. package/lib/deltaManager.js +75 -49
  137. package/lib/deltaManager.js.map +1 -1
  138. package/lib/deltaManagerFactory.d.ts +23 -0
  139. package/lib/deltaManagerFactory.d.ts.map +1 -0
  140. package/lib/deltaManagerFactory.js +14 -0
  141. package/lib/deltaManagerFactory.js.map +1 -0
  142. package/lib/deltaQueue.js +14 -7
  143. package/lib/deltaQueue.js.map +1 -1
  144. package/lib/error.js +5 -4
  145. package/lib/error.js.map +1 -1
  146. package/lib/frozenServices.d.ts +2 -2
  147. package/lib/frozenServices.d.ts.map +1 -1
  148. package/lib/frozenServices.js +48 -30
  149. package/lib/frozenServices.js.map +1 -1
  150. package/lib/index.d.ts +1 -1
  151. package/lib/index.d.ts.map +1 -1
  152. package/lib/index.js +1 -1
  153. package/lib/index.js.map +1 -1
  154. package/lib/legacy.d.ts +1 -1
  155. package/lib/legacyAlpha.d.ts +1 -1
  156. package/lib/loadContainerToSequenceNumber.d.ts +6 -5
  157. package/lib/loadContainerToSequenceNumber.d.ts.map +1 -1
  158. package/lib/loadContainerToSequenceNumber.js +4 -4
  159. package/lib/loadContainerToSequenceNumber.js.map +1 -1
  160. package/lib/loader.js +6 -2
  161. package/lib/loader.js.map +1 -1
  162. package/lib/loaderLayerCompatState.d.ts +25 -1
  163. package/lib/loaderLayerCompatState.d.ts.map +1 -1
  164. package/lib/loaderLayerCompatState.js +76 -9
  165. package/lib/loaderLayerCompatState.js.map +1 -1
  166. package/lib/noopHeuristic.js +3 -1
  167. package/lib/noopHeuristic.js.map +1 -1
  168. package/lib/packageVersion.d.ts +1 -1
  169. package/lib/packageVersion.d.ts.map +1 -1
  170. package/lib/packageVersion.js +1 -1
  171. package/lib/packageVersion.js.map +1 -1
  172. package/lib/pendingLocalStateStore.d.ts +4 -3
  173. package/lib/pendingLocalStateStore.d.ts.map +1 -1
  174. package/lib/pendingLocalStateStore.js +26 -37
  175. package/lib/pendingLocalStateStore.js.map +1 -1
  176. package/lib/pointInTimeServices.js +2 -0
  177. package/lib/pointInTimeServices.js.map +1 -1
  178. package/lib/protocol/protocol.js +3 -0
  179. package/lib/protocol/protocol.js.map +1 -1
  180. package/lib/protocol/quorum.js +26 -5
  181. package/lib/protocol/quorum.js.map +1 -1
  182. package/lib/protocol.js +2 -0
  183. package/lib/protocol.js.map +1 -1
  184. package/lib/protocolTreeDocumentStorageService.js +10 -0
  185. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  186. package/lib/public.d.ts +1 -1
  187. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  188. package/lib/retriableDocumentStorageService.js +5 -1
  189. package/lib/retriableDocumentStorageService.js.map +1 -1
  190. package/lib/serializedStateManager.d.ts +6 -0
  191. package/lib/serializedStateManager.d.ts.map +1 -1
  192. package/lib/serializedStateManager.js +15 -20
  193. package/lib/serializedStateManager.js.map +1 -1
  194. package/lib/snapshotRefresher.js +31 -36
  195. package/lib/snapshotRefresher.js.map +1 -1
  196. package/lib/utils.d.ts +1 -1
  197. package/lib/utils.d.ts.map +1 -1
  198. package/lib/utils.js +3 -3
  199. package/lib/utils.js.map +1 -1
  200. package/package.json +44 -106
  201. package/src/container.ts +35 -22
  202. package/src/createAndLoadContainerUtils.ts +131 -46
  203. package/src/debugLogger.ts +39 -40
  204. package/src/deltaManager.ts +18 -5
  205. package/src/deltaManagerFactory.ts +53 -0
  206. package/src/frozenServices.ts +6 -7
  207. package/src/index.ts +1 -0
  208. package/src/loadContainerToSequenceNumber.ts +7 -6
  209. package/src/loader.ts +2 -2
  210. package/src/loaderLayerCompatState.ts +85 -20
  211. package/src/packageVersion.ts +1 -1
  212. package/src/pendingLocalStateStore.ts +7 -3
  213. package/src/pointInTime/DEV.md +47 -4
  214. package/src/serializedStateManager.ts +6 -0
  215. package/src/utils.ts +3 -3
  216. package/tsconfig.json +1 -1
  217. package/internal.d.ts +0 -11
  218. package/legacy/alpha.d.ts +0 -11
  219. package/legacy.d.ts +0 -11
  220. /package/api-extractor/{api-extractor.current.json → api-extractor-report.current.json} +0 -0
  221. /package/api-extractor/{api-extractor.legacy.json → api-extractor-report.legacy.json} +0 -0
@@ -2,18 +2,6 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
6
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
7
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
8
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
9
- };
10
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
11
- if (kind === "m") throw new TypeError("Private method is not writable");
12
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
13
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
14
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
15
- };
16
- var _PendingLocalStateStore_firstUrl, _PendingLocalStateStore_pendingStates, _PendingLocalStateStore_savedOps, _PendingLocalStateStore_blobs, _PendingLocalStateStore_attachmentBlobs, _PendingLocalStateStore_loadingGroups;
17
5
  import { UsageError } from "@fluidframework/telemetry-utils/internal";
18
6
  import { getAttachedContainerStateFromSerializedContainer } from "./utils.js";
19
7
  /**
@@ -45,19 +33,17 @@ import { getAttachedContainerStateFromSerializedContainer } from "./utils.js";
45
33
  * @legacy @alpha
46
34
  */
47
35
  export class PendingLocalStateStore {
48
- constructor() {
49
- _PendingLocalStateStore_firstUrl.set(this, void 0);
50
- _PendingLocalStateStore_pendingStates.set(this, new Map());
51
- _PendingLocalStateStore_savedOps.set(this, {});
52
- _PendingLocalStateStore_blobs.set(this, {});
53
- _PendingLocalStateStore_attachmentBlobs.set(this, {});
54
- _PendingLocalStateStore_loadingGroups.set(this, {});
55
- }
36
+ #firstUrl;
37
+ #pendingStates = new Map();
38
+ #savedOps = {};
39
+ #blobs = {};
40
+ #attachmentBlobs = {};
41
+ #loadingGroups = {};
56
42
  /**
57
43
  * Removes all stored pending states.
58
44
  */
59
45
  clear() {
60
- return __classPrivateFieldGet(this, _PendingLocalStateStore_pendingStates, "f").clear();
46
+ return this.#pendingStates.clear();
61
47
  }
62
48
  /**
63
49
  * Removes the pending state for the specified key.
@@ -66,7 +52,7 @@ export class PendingLocalStateStore {
66
52
  * @returns `true` if the state existed and was removed, `false` otherwise
67
53
  */
68
54
  delete(key) {
69
- return __classPrivateFieldGet(this, _PendingLocalStateStore_pendingStates, "f").delete(key);
55
+ return this.#pendingStates.delete(key);
70
56
  }
71
57
  /**
72
58
  * Retrieves the serialized pending state for the specified key.
@@ -75,13 +61,13 @@ export class PendingLocalStateStore {
75
61
  * @returns The serialized state as a JSON string, or `undefined` if not found
76
62
  */
77
63
  get(key) {
78
- return JSON.stringify(__classPrivateFieldGet(this, _PendingLocalStateStore_pendingStates, "f").get(key));
64
+ return JSON.stringify(this.#pendingStates.get(key));
79
65
  }
80
66
  /**
81
67
  * Checks whether a pending state exists for the specified key.
82
68
  */
83
69
  has(key) {
84
- return __classPrivateFieldGet(this, _PendingLocalStateStore_pendingStates, "f").has(key);
70
+ return this.#pendingStates.has(key);
85
71
  }
86
72
  /**
87
73
  * Stores a pending state from `container.getPendingLocalState()`.
@@ -97,44 +83,44 @@ export class PendingLocalStateStore {
97
83
  const { savedOps, snapshotBlobs, attachmentBlobContents, loadedGroupIdSnapshots, url } = state;
98
84
  // Normalize URL by removing trailing slash for comparison
99
85
  const normalizedUrl = url.replace(/\/$/, "");
100
- const normalizedFirstUrl = __classPrivateFieldGet(this, _PendingLocalStateStore_firstUrl, "f")?.replace(/\/$/, "");
101
- __classPrivateFieldSet(this, _PendingLocalStateStore_firstUrl, __classPrivateFieldGet(this, _PendingLocalStateStore_firstUrl, "f") ?? url, "f");
86
+ const normalizedFirstUrl = this.#firstUrl?.replace(/\/$/, "");
87
+ this.#firstUrl ??= url;
102
88
  if (normalizedFirstUrl !== undefined && normalizedFirstUrl !== normalizedUrl) {
103
89
  throw new UsageError("PendingLocalStateStore can only be used with a single container.");
104
90
  }
105
91
  for (let i = 0; i < savedOps.length; i++) {
106
- savedOps[i] = __classPrivateFieldGet(this, _PendingLocalStateStore_savedOps, "f")[savedOps[i].sequenceNumber] ??= savedOps[i];
92
+ savedOps[i] = this.#savedOps[savedOps[i].sequenceNumber] ??= savedOps[i];
107
93
  }
108
94
  for (const [id, blob] of Object.entries(snapshotBlobs)) {
109
- snapshotBlobs[id] = __classPrivateFieldGet(this, _PendingLocalStateStore_blobs, "f")[id] ??= blob;
95
+ snapshotBlobs[id] = this.#blobs[id] ??= blob;
110
96
  }
111
97
  if (attachmentBlobContents !== undefined) {
112
98
  for (const [id, blob] of Object.entries(attachmentBlobContents)) {
113
- attachmentBlobContents[id] = __classPrivateFieldGet(this, _PendingLocalStateStore_attachmentBlobs, "f")[id] ??= blob;
99
+ attachmentBlobContents[id] = this.#attachmentBlobs[id] ??= blob;
114
100
  }
115
101
  }
116
102
  if (loadedGroupIdSnapshots !== undefined) {
117
103
  for (const [id, lg] of Object.entries(loadedGroupIdSnapshots)) {
118
- if (__classPrivateFieldGet(this, _PendingLocalStateStore_loadingGroups, "f")[id] === undefined ||
119
- lg.snapshotSequenceNumber < __classPrivateFieldGet(this, _PendingLocalStateStore_loadingGroups, "f")[id].snapshotSequenceNumber) {
120
- loadedGroupIdSnapshots[id] = __classPrivateFieldGet(this, _PendingLocalStateStore_loadingGroups, "f")[id] = lg;
104
+ if (this.#loadingGroups[id] === undefined ||
105
+ lg.snapshotSequenceNumber < this.#loadingGroups[id].snapshotSequenceNumber) {
106
+ loadedGroupIdSnapshots[id] = this.#loadingGroups[id] = lg;
121
107
  }
122
108
  }
123
109
  }
124
- __classPrivateFieldGet(this, _PendingLocalStateStore_pendingStates, "f").set(key, state);
110
+ this.#pendingStates.set(key, state);
125
111
  return this;
126
112
  }
127
113
  /**
128
114
  * Gets the number of stored pending states.
129
115
  */
130
116
  get size() {
131
- return __classPrivateFieldGet(this, _PendingLocalStateStore_pendingStates, "f").size;
117
+ return this.#pendingStates.size;
132
118
  }
133
119
  /**
134
120
  * Returns an iterator over [key, serializedState] pairs.
135
121
  */
136
122
  entries() {
137
- const iterator = __classPrivateFieldGet(this, _PendingLocalStateStore_pendingStates, "f").entries();
123
+ const iterator = this.#pendingStates.entries();
138
124
  return {
139
125
  next: () => {
140
126
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -144,18 +130,21 @@ export class PendingLocalStateStore {
144
130
  }
145
131
  return { done, value: [value[0], JSON.stringify(value[1])] };
146
132
  },
133
+ [Symbol.iterator]() {
134
+ return this;
135
+ },
147
136
  };
148
137
  }
149
138
  /**
150
139
  * Returns an iterator over the stored keys.
151
140
  */
152
141
  keys() {
153
- return __classPrivateFieldGet(this, _PendingLocalStateStore_pendingStates, "f").keys();
142
+ return this.#pendingStates.keys();
154
143
  }
155
144
  /**
156
145
  * Makes the store iterable with `for...of` loops.
157
146
  */
158
- [(_PendingLocalStateStore_firstUrl = new WeakMap(), _PendingLocalStateStore_pendingStates = new WeakMap(), _PendingLocalStateStore_savedOps = new WeakMap(), _PendingLocalStateStore_blobs = new WeakMap(), _PendingLocalStateStore_attachmentBlobs = new WeakMap(), _PendingLocalStateStore_loadingGroups = new WeakMap(), Symbol.iterator)]() {
147
+ [Symbol.iterator]() {
159
148
  return this.entries();
160
149
  }
161
150
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pendingLocalStateStore.js","sourceRoot":"","sources":["../src/pendingLocalStateStore.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport { UsageError } from \"@fluidframework/telemetry-utils/internal\";\n\nimport type {\n\tIPendingContainerState,\n\tSerializedSnapshotInfo,\n} from \"./serializedStateManager.js\";\nimport { getAttachedContainerStateFromSerializedContainer } from \"./utils.js\";\n\n/**\n * A Map-like store for managing pending local container states from attached containers.\n * Optimizes storage by deduplicating shared resources across stored states.\n *\n * @example\n * ```typescript\n * const store = new PendingLocalStateStore<string>();\n *\n * // Store pending state\n * const pendingState = await attachedContainer.getPendingLocalState();\n * store.set(\"session1\", pendingState);\n *\n * // Load from stored state\n * const restored = store.get(\"session1\");\n * const newContainer = await loadFrozenContainerFromPendingState({\n * pendingLocalState: restored,\n * // ... other loader options\n * });\n * ```\n *\n * @remarks\n * Only use with attached containers from the same URL. Only store strings\n * returned by `container.getPendingLocalState()`.\n *\n * @typeParam TKey - The type of keys used to identify stored states\n *\n * @legacy @alpha\n */\nexport class PendingLocalStateStore<TKey> {\n\t#firstUrl: string | undefined;\n\treadonly #pendingStates = new Map<TKey, IPendingContainerState>();\n\treadonly #savedOps: Record<number, ISequencedDocumentMessage> = {};\n\treadonly #blobs: Record<string, string> = {};\n\treadonly #attachmentBlobs: Record<string, string> = {};\n\treadonly #loadingGroups: Record<string, SerializedSnapshotInfo> = {};\n\n\t/**\n\t * Removes all stored pending states.\n\t */\n\tclear(): void {\n\t\treturn this.#pendingStates.clear();\n\t}\n\n\t/**\n\t * Removes the pending state for the specified key.\n\t *\n\t * @param key - The key to remove\n\t * @returns `true` if the state existed and was removed, `false` otherwise\n\t */\n\tdelete(key: TKey): boolean {\n\t\treturn this.#pendingStates.delete(key);\n\t}\n\n\t/**\n\t * Retrieves the serialized pending state for the specified key.\n\t *\n\t * @param key - The key to retrieve\n\t * @returns The serialized state as a JSON string, or `undefined` if not found\n\t */\n\tget(key: TKey): string | undefined {\n\t\treturn JSON.stringify(this.#pendingStates.get(key));\n\t}\n\n\t/**\n\t * Checks whether a pending state exists for the specified key.\n\t */\n\thas(key: TKey): boolean {\n\t\treturn this.#pendingStates.has(key);\n\t}\n\n\t/**\n\t * Stores a pending state from `container.getPendingLocalState()`.\n\t *\n\t * @param key - The key to associate with the state\n\t * @param pendingLocalState - String returned by `getPendingLocalState()` from an attached container\n\t * @returns This store instance for method chaining\n\t *\n\t * @throws When storing states from different container URLs\n\t */\n\tset(key: TKey, pendingLocalState: string): this {\n\t\tconst state = getAttachedContainerStateFromSerializedContainer(pendingLocalState);\n\t\tconst { savedOps, snapshotBlobs, attachmentBlobContents, loadedGroupIdSnapshots, url } =\n\t\t\tstate;\n\n\t\t// Normalize URL by removing trailing slash for comparison\n\t\tconst normalizedUrl = url.replace(/\\/$/, \"\");\n\t\tconst normalizedFirstUrl = this.#firstUrl?.replace(/\\/$/, \"\");\n\t\tthis.#firstUrl ??= url;\n\t\tif (normalizedFirstUrl !== undefined && normalizedFirstUrl !== normalizedUrl) {\n\t\t\tthrow new UsageError(\"PendingLocalStateStore can only be used with a single container.\");\n\t\t}\n\n\t\tfor (let i = 0; i < savedOps.length; i++) {\n\t\t\tsavedOps[i] = this.#savedOps[savedOps[i].sequenceNumber] ??= savedOps[i];\n\t\t}\n\t\tfor (const [id, blob] of Object.entries(snapshotBlobs)) {\n\t\t\tsnapshotBlobs[id] = this.#blobs[id] ??= blob;\n\t\t}\n\t\tif (attachmentBlobContents !== undefined) {\n\t\t\tfor (const [id, blob] of Object.entries(attachmentBlobContents)) {\n\t\t\t\tattachmentBlobContents[id] = this.#attachmentBlobs[id] ??= blob;\n\t\t\t}\n\t\t}\n\t\tif (loadedGroupIdSnapshots !== undefined) {\n\t\t\tfor (const [id, lg] of Object.entries(loadedGroupIdSnapshots)) {\n\t\t\t\tif (\n\t\t\t\t\tthis.#loadingGroups[id] === undefined ||\n\t\t\t\t\tlg.snapshotSequenceNumber < this.#loadingGroups[id].snapshotSequenceNumber\n\t\t\t\t) {\n\t\t\t\t\tloadedGroupIdSnapshots[id] = this.#loadingGroups[id] = lg;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.#pendingStates.set(key, state);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the number of stored pending states.\n\t */\n\tget size(): number {\n\t\treturn this.#pendingStates.size;\n\t}\n\n\t/**\n\t * Returns an iterator over [key, serializedState] pairs.\n\t */\n\tentries(): Iterator<[TKey, string]> {\n\t\tconst iterator = this.#pendingStates.entries();\n\t\treturn {\n\t\t\tnext: (): IteratorResult<[TKey, string]> => {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tconst { done, value } = iterator.next();\n\t\t\t\tif (done === true) {\n\t\t\t\t\treturn { done, value: undefined };\n\t\t\t\t}\n\t\t\t\treturn { done, value: [value[0], JSON.stringify(value[1])] };\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Returns an iterator over the stored keys.\n\t */\n\tkeys(): IterableIterator<TKey> {\n\t\treturn this.#pendingStates.keys();\n\t}\n\n\t/**\n\t * Makes the store iterable with `for...of` loops.\n\t */\n\t[Symbol.iterator](): Iterator<[TKey, string]> {\n\t\treturn this.entries();\n\t}\n}\n"]}
1
+ {"version":3,"file":"pendingLocalStateStore.js","sourceRoot":"","sources":["../src/pendingLocalStateStore.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { FluidIterableIterator } from \"@fluidframework/core-interfaces\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\nimport { UsageError } from \"@fluidframework/telemetry-utils/internal\";\n\nimport type {\n\tIPendingContainerState,\n\tSerializedSnapshotInfo,\n} from \"./serializedStateManager.js\";\nimport { getAttachedContainerStateFromSerializedContainer } from \"./utils.js\";\n\n/**\n * A Map-like store for managing pending local container states from attached containers.\n * Optimizes storage by deduplicating shared resources across stored states.\n *\n * @example\n * ```typescript\n * const store = new PendingLocalStateStore<string>();\n *\n * // Store pending state\n * const pendingState = await attachedContainer.getPendingLocalState();\n * store.set(\"session1\", pendingState);\n *\n * // Load from stored state\n * const restored = store.get(\"session1\");\n * const newContainer = await loadFrozenContainerFromPendingState({\n * pendingLocalState: restored,\n * // ... other loader options\n * });\n * ```\n *\n * @remarks\n * Only use with attached containers from the same URL. Only store strings\n * returned by `container.getPendingLocalState()`.\n *\n * @typeParam TKey - The type of keys used to identify stored states\n *\n * @legacy @alpha\n */\nexport class PendingLocalStateStore<TKey> {\n\t#firstUrl: string | undefined;\n\treadonly #pendingStates = new Map<TKey, IPendingContainerState>();\n\treadonly #savedOps: Record<number, ISequencedDocumentMessage> = {};\n\treadonly #blobs: Record<string, string> = {};\n\treadonly #attachmentBlobs: Record<string, string> = {};\n\treadonly #loadingGroups: Record<string, SerializedSnapshotInfo> = {};\n\n\t/**\n\t * Removes all stored pending states.\n\t */\n\tclear(): void {\n\t\treturn this.#pendingStates.clear();\n\t}\n\n\t/**\n\t * Removes the pending state for the specified key.\n\t *\n\t * @param key - The key to remove\n\t * @returns `true` if the state existed and was removed, `false` otherwise\n\t */\n\tdelete(key: TKey): boolean {\n\t\treturn this.#pendingStates.delete(key);\n\t}\n\n\t/**\n\t * Retrieves the serialized pending state for the specified key.\n\t *\n\t * @param key - The key to retrieve\n\t * @returns The serialized state as a JSON string, or `undefined` if not found\n\t */\n\tget(key: TKey): string | undefined {\n\t\treturn JSON.stringify(this.#pendingStates.get(key));\n\t}\n\n\t/**\n\t * Checks whether a pending state exists for the specified key.\n\t */\n\thas(key: TKey): boolean {\n\t\treturn this.#pendingStates.has(key);\n\t}\n\n\t/**\n\t * Stores a pending state from `container.getPendingLocalState()`.\n\t *\n\t * @param key - The key to associate with the state\n\t * @param pendingLocalState - String returned by `getPendingLocalState()` from an attached container\n\t * @returns This store instance for method chaining\n\t *\n\t * @throws When storing states from different container URLs\n\t */\n\tset(key: TKey, pendingLocalState: string): this {\n\t\tconst state = getAttachedContainerStateFromSerializedContainer(pendingLocalState);\n\t\tconst { savedOps, snapshotBlobs, attachmentBlobContents, loadedGroupIdSnapshots, url } =\n\t\t\tstate;\n\n\t\t// Normalize URL by removing trailing slash for comparison\n\t\tconst normalizedUrl = url.replace(/\\/$/, \"\");\n\t\tconst normalizedFirstUrl = this.#firstUrl?.replace(/\\/$/, \"\");\n\t\tthis.#firstUrl ??= url;\n\t\tif (normalizedFirstUrl !== undefined && normalizedFirstUrl !== normalizedUrl) {\n\t\t\tthrow new UsageError(\"PendingLocalStateStore can only be used with a single container.\");\n\t\t}\n\n\t\tfor (let i = 0; i < savedOps.length; i++) {\n\t\t\tsavedOps[i] = this.#savedOps[savedOps[i].sequenceNumber] ??= savedOps[i];\n\t\t}\n\t\tfor (const [id, blob] of Object.entries(snapshotBlobs)) {\n\t\t\tsnapshotBlobs[id] = this.#blobs[id] ??= blob;\n\t\t}\n\t\tif (attachmentBlobContents !== undefined) {\n\t\t\tfor (const [id, blob] of Object.entries(attachmentBlobContents)) {\n\t\t\t\tattachmentBlobContents[id] = this.#attachmentBlobs[id] ??= blob;\n\t\t\t}\n\t\t}\n\t\tif (loadedGroupIdSnapshots !== undefined) {\n\t\t\tfor (const [id, lg] of Object.entries(loadedGroupIdSnapshots)) {\n\t\t\t\tif (\n\t\t\t\t\tthis.#loadingGroups[id] === undefined ||\n\t\t\t\t\tlg.snapshotSequenceNumber < this.#loadingGroups[id].snapshotSequenceNumber\n\t\t\t\t) {\n\t\t\t\t\tloadedGroupIdSnapshots[id] = this.#loadingGroups[id] = lg;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.#pendingStates.set(key, state);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the number of stored pending states.\n\t */\n\tget size(): number {\n\t\treturn this.#pendingStates.size;\n\t}\n\n\t/**\n\t * Returns an iterator over [key, serializedState] pairs.\n\t */\n\tentries(): FluidIterableIterator<[TKey, string]> {\n\t\tconst iterator = this.#pendingStates.entries();\n\t\treturn {\n\t\t\tnext: (): IteratorResult<[TKey, string]> => {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tconst { done, value } = iterator.next();\n\t\t\t\tif (done === true) {\n\t\t\t\t\treturn { done, value: undefined };\n\t\t\t\t}\n\t\t\t\treturn { done, value: [value[0], JSON.stringify(value[1])] };\n\t\t\t},\n\t\t\t[Symbol.iterator](): FluidIterableIterator<[TKey, string]> {\n\t\t\t\treturn this;\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Returns an iterator over the stored keys.\n\t */\n\tkeys(): FluidIterableIterator<TKey> {\n\t\treturn this.#pendingStates.keys();\n\t}\n\n\t/**\n\t * Makes the store iterable with `for...of` loops.\n\t */\n\t[Symbol.iterator](): FluidIterableIterator<[TKey, string]> {\n\t\treturn this.entries();\n\t}\n}\n"]}
@@ -24,6 +24,8 @@ export function asPointInTimeCapableFactory(factory) {
24
24
  * {@link loadContainerToSequenceNumber}) - they never build or pass a decorated factory themselves.
25
25
  */
26
26
  export class PointInTimeDocumentServiceFactory {
27
+ inner;
28
+ targetSequenceNumber;
27
29
  constructor(inner, targetSequenceNumber) {
28
30
  this.inner = inner;
29
31
  this.targetSequenceNumber = targetSequenceNumber;
@@ -1 +1 @@
1
- {"version":3,"file":"pointInTimeServices.js","sourceRoot":"","sources":["../src/pointInTimeServices.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAyBnE;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAC1C,OAAgC;IAEhC,OAAO,OAAQ,OAA8D;SAC3E,gCAAgC,KAAK,UAAU;QAChD,CAAC,CAAE,OAA+E;QAClF,CAAC,CAAC,SAAS,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,iCAAiC;IAC7C,YACkB,KAAgD,EAChD,oBAA4B;QAD5B,UAAK,GAAL,KAAK,CAA2C;QAChD,yBAAoB,GAApB,oBAAoB,CAAQ;IAC3C,CAAC;IAEG,KAAK,CAAC,qBAAqB,CACjC,WAAyB,EACzB,MAA6B,EAC7B,kBAA4B;QAE5B,OAAO,IAAI,CAAC,KAAK,CAAC,gCAAgC,CACjD,WAAW,EACX,IAAI,CAAC,oBAAoB,EACzB,MAAM,EACN,kBAAkB,CAClB,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,eAAe;QAC3B,MAAM,IAAI,UAAU,CACnB,+EAA+E,CAC/E,CAAC;IACH,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport type {\n\tIDocumentService,\n\tIDocumentServiceFactory,\n\tIResolvedUrl,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { UsageError } from \"@fluidframework/driver-utils/internal\";\n\n/**\n * A document service factory that can materialize a document at a specific sequence number.\n *\n * @remarks\n * This is a capability a driver's {@link IDocumentServiceFactory} may additionally implement. The\n * loader detects it (see {@link asPointInTimeCapableFactory}) so that\n * {@link loadContainerToSequenceNumber} can drive point-in-time loading without the caller having to\n * wrap or decorate their factory. The driver owns selecting a snapshot at or before the target and\n * replaying the remaining ops on top of it.\n */\ninterface IPointInTimeCapableDocumentServiceFactory {\n\t/**\n\t * Creates a read-only document service materialized at `targetSequenceNumber`: storage serves a\n\t * snapshot at or before the target and delta storage supplies the ops needed to advance to it.\n\t */\n\tcreatePointInTimeDocumentService(\n\t\tresolvedUrl: IResolvedUrl,\n\t\ttargetSequenceNumber: number,\n\t\tlogger?: ITelemetryBaseLogger,\n\t\tclientIsSummarizer?: boolean,\n\t): Promise<IDocumentService>;\n}\n\n/**\n * Returns the factory typed as point-in-time capable if it implements\n * {@link IPointInTimeCapableDocumentServiceFactory}, otherwise `undefined`.\n */\nexport function asPointInTimeCapableFactory(\n\tfactory: IDocumentServiceFactory,\n): (IDocumentServiceFactory & IPointInTimeCapableDocumentServiceFactory) | undefined {\n\treturn typeof (factory as Partial<IPointInTimeCapableDocumentServiceFactory>)\n\t\t.createPointInTimeDocumentService === \"function\"\n\t\t? (factory as IDocumentServiceFactory & IPointInTimeCapableDocumentServiceFactory)\n\t\t: undefined;\n}\n\n/**\n * Adapts a point-in-time capable factory to a plain {@link IDocumentServiceFactory} that always\n * materializes the configured target sequence number.\n *\n * @remarks\n * The loader plumbs the caller's target through this adapter so the container's normal\n * `createDocumentService(resolvedUrl)` call routes to `createPointInTimeDocumentService`. This keeps\n * point-in-time loading a single-call experience for the caller (see\n * {@link loadContainerToSequenceNumber}) - they never build or pass a decorated factory themselves.\n */\nexport class PointInTimeDocumentServiceFactory implements IDocumentServiceFactory {\n\tpublic constructor(\n\t\tprivate readonly inner: IPointInTimeCapableDocumentServiceFactory,\n\t\tprivate readonly targetSequenceNumber: number,\n\t) {}\n\n\tpublic async createDocumentService(\n\t\tresolvedUrl: IResolvedUrl,\n\t\tlogger?: ITelemetryBaseLogger,\n\t\tclientIsSummarizer?: boolean,\n\t): Promise<IDocumentService> {\n\t\treturn this.inner.createPointInTimeDocumentService(\n\t\t\tresolvedUrl,\n\t\t\tthis.targetSequenceNumber,\n\t\t\tlogger,\n\t\t\tclientIsSummarizer,\n\t\t);\n\t}\n\n\tpublic async createContainer(): Promise<IDocumentService> {\n\t\tthrow new UsageError(\n\t\t\t\"A point-in-time document service factory cannot be used to create containers.\",\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"pointInTimeServices.js","sourceRoot":"","sources":["../src/pointInTimeServices.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAyBnE;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAC1C,OAAgC;IAEhC,OAAO,OAAQ,OAA8D;SAC3E,gCAAgC,KAAK,UAAU;QAChD,CAAC,CAAE,OAA+E;QAClF,CAAC,CAAC,SAAS,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,iCAAiC;IAE3B;IACA;IAFlB,YACkB,KAAgD,EAChD,oBAA4B;QAD5B,UAAK,GAAL,KAAK,CAA2C;QAChD,yBAAoB,GAApB,oBAAoB,CAAQ;IAC3C,CAAC;IAEG,KAAK,CAAC,qBAAqB,CACjC,WAAyB,EACzB,MAA6B,EAC7B,kBAA4B;QAE5B,OAAO,IAAI,CAAC,KAAK,CAAC,gCAAgC,CACjD,WAAW,EACX,IAAI,CAAC,oBAAoB,EACzB,MAAM,EACN,kBAAkB,CAClB,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,eAAe;QAC3B,MAAM,IAAI,UAAU,CACnB,+EAA+E,CAC/E,CAAC;IACH,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport type {\n\tIDocumentService,\n\tIDocumentServiceFactory,\n\tIResolvedUrl,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { UsageError } from \"@fluidframework/driver-utils/internal\";\n\n/**\n * A document service factory that can materialize a document at a specific sequence number.\n *\n * @remarks\n * This is a capability a driver's {@link IDocumentServiceFactory} may additionally implement. The\n * loader detects it (see {@link asPointInTimeCapableFactory}) so that\n * {@link loadContainerToSequenceNumber} can drive point-in-time loading without the caller having to\n * wrap or decorate their factory. The driver owns selecting a snapshot at or before the target and\n * replaying the remaining ops on top of it.\n */\ninterface IPointInTimeCapableDocumentServiceFactory {\n\t/**\n\t * Creates a read-only document service materialized at `targetSequenceNumber`: storage serves a\n\t * snapshot at or before the target and delta storage supplies the ops needed to advance to it.\n\t */\n\tcreatePointInTimeDocumentService(\n\t\tresolvedUrl: IResolvedUrl,\n\t\ttargetSequenceNumber: number,\n\t\tlogger?: ITelemetryBaseLogger,\n\t\tclientIsSummarizer?: boolean,\n\t): Promise<IDocumentService>;\n}\n\n/**\n * Returns the factory typed as point-in-time capable if it implements\n * {@link IPointInTimeCapableDocumentServiceFactory}, otherwise `undefined`.\n */\nexport function asPointInTimeCapableFactory(\n\tfactory: IDocumentServiceFactory,\n): (IDocumentServiceFactory & IPointInTimeCapableDocumentServiceFactory) | undefined {\n\treturn typeof (factory as Partial<IPointInTimeCapableDocumentServiceFactory>)\n\t\t.createPointInTimeDocumentService === \"function\"\n\t\t? (factory as IDocumentServiceFactory & IPointInTimeCapableDocumentServiceFactory)\n\t\t: undefined;\n}\n\n/**\n * Adapts a point-in-time capable factory to a plain {@link IDocumentServiceFactory} that always\n * materializes the configured target sequence number.\n *\n * @remarks\n * The loader plumbs the caller's target through this adapter so the container's normal\n * `createDocumentService(resolvedUrl)` call routes to `createPointInTimeDocumentService`. This keeps\n * point-in-time loading a single-call experience for the caller (see\n * {@link loadContainerToSequenceNumber}) - they never build or pass a decorated factory themselves.\n */\nexport class PointInTimeDocumentServiceFactory implements IDocumentServiceFactory {\n\tpublic constructor(\n\t\tprivate readonly inner: IPointInTimeCapableDocumentServiceFactory,\n\t\tprivate readonly targetSequenceNumber: number,\n\t) {}\n\n\tpublic async createDocumentService(\n\t\tresolvedUrl: IResolvedUrl,\n\t\tlogger?: ITelemetryBaseLogger,\n\t\tclientIsSummarizer?: boolean,\n\t): Promise<IDocumentService> {\n\t\treturn this.inner.createPointInTimeDocumentService(\n\t\t\tresolvedUrl,\n\t\t\tthis.targetSequenceNumber,\n\t\t\tlogger,\n\t\t\tclientIsSummarizer,\n\t\t);\n\t}\n\n\tpublic async createContainer(): Promise<IDocumentService> {\n\t\tthrow new UsageError(\n\t\t\t\"A point-in-time document service factory cannot be used to create containers.\",\n\t\t);\n\t}\n}\n"]}
@@ -9,6 +9,9 @@ import { Quorum } from "./quorum.js";
9
9
  * @internal
10
10
  */
11
11
  export class ProtocolOpHandler {
12
+ minimumSequenceNumber;
13
+ sequenceNumber;
14
+ _quorum;
12
15
  get quorum() {
13
16
  return this._quorum;
14
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ISequencedClient } from \"@fluidframework/driver-definitions\";\nimport {\n\ttype ISequencedDocumentMessage,\n\ttype IDocumentAttributes,\n\ttype IClientJoin,\n\ttype ICommittedProposal,\n\ttype IProcessMessageResult,\n\ttype IProposal,\n\ttype IQuorum,\n\ttype ISequencedDocumentSystemMessage,\n\ttype ISequencedProposal,\n\tMessageType,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport { type IQuorumSnapshot, Quorum } from \"./quorum.js\";\n\n/**\n * @legacy @beta\n */\nexport interface IScribeProtocolState {\n\tsequenceNumber: number;\n\tminimumSequenceNumber: number;\n\tmembers: [string, ISequencedClient][];\n\tproposals: [number, ISequencedProposal, string[]][];\n\tvalues: [string, ICommittedProposal][];\n}\n\n/**\n * @legacy @beta\n */\nexport interface IProtocolHandler {\n\treadonly quorum: IQuorum;\n\treadonly attributes: IDocumentAttributes;\n\n\tsetConnectionState(connected: boolean, clientId: string | undefined);\n\tsnapshot(): IQuorumSnapshot;\n\n\tclose(): void;\n\tprocessMessage(message: ISequencedDocumentMessage, local: boolean): IProcessMessageResult;\n\tgetProtocolState(): IScribeProtocolState;\n}\n\n/**\n * Handles protocol specific ops.\n * @internal\n */\nexport class ProtocolOpHandler implements IProtocolHandler {\n\tprivate readonly _quorum: Quorum;\n\tpublic get quorum(): Quorum {\n\t\treturn this._quorum;\n\t}\n\n\tconstructor(\n\t\tpublic minimumSequenceNumber: number,\n\t\tpublic sequenceNumber: number,\n\t\tmembers: [string, ISequencedClient][],\n\t\tproposals: [number, ISequencedProposal, string[]][],\n\t\tvalues: [string, ICommittedProposal][],\n\t\tsendProposal: (key: string, value: unknown) => number,\n\t) {\n\t\tthis._quorum = new Quorum(members, proposals, values, sendProposal);\n\t}\n\n\tpublic get attributes(): IDocumentAttributes {\n\t\treturn {\n\t\t\tminimumSequenceNumber: this.minimumSequenceNumber,\n\t\t\tsequenceNumber: this.sequenceNumber,\n\t\t};\n\t}\n\n\tsetConnectionState(connected: boolean, clientId: string | undefined): void {\n\t\tthis._quorum.setConnectionState(connected, clientId);\n\t}\n\n\tsnapshot(): IQuorumSnapshot {\n\t\treturn this._quorum.snapshot();\n\t}\n\n\tpublic close(): void {\n\t\tthis._quorum.close();\n\t}\n\n\tpublic processMessage(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tlocal: boolean,\n\t): IProcessMessageResult {\n\t\t// verify it's moving sequentially\n\t\tif (message.sequenceNumber !== this.sequenceNumber + 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`Protocol state is not moving sequentially. ` +\n\t\t\t\t\t`Current is ${this.sequenceNumber}. Next is ${message.sequenceNumber}`,\n\t\t\t);\n\t\t}\n\n\t\t// Update tracked sequence numbers\n\t\tthis.sequenceNumber = message.sequenceNumber;\n\t\tthis.minimumSequenceNumber = message.minimumSequenceNumber;\n\n\t\tlet immediateNoOp = false;\n\n\t\tswitch (message.type) {\n\t\t\tcase MessageType.ClientJoin: {\n\t\t\t\tconst systemJoinMessage = message as ISequencedDocumentSystemMessage;\n\t\t\t\tconst join = JSON.parse(systemJoinMessage.data) as IClientJoin;\n\t\t\t\tconst member: ISequencedClient = {\n\t\t\t\t\tclient: join.detail,\n\t\t\t\t\tsequenceNumber: systemJoinMessage.sequenceNumber,\n\t\t\t\t};\n\t\t\t\tthis._quorum.addMember(join.clientId, member);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MessageType.ClientLeave: {\n\t\t\t\tconst systemLeaveMessage = message as ISequencedDocumentSystemMessage;\n\t\t\t\tconst clientId = JSON.parse(systemLeaveMessage.data) as string;\n\t\t\t\tthis._quorum.removeMember(clientId);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MessageType.Propose: {\n\t\t\t\t// DeltaManager is no longer parsing the contents, so we expect this to be a string here\n\t\t\t\t// (However, switching this to an assert caused some tests to fail, apparently due to the same\n\t\t\t\t// message object being processed multiple times - So we will retain the typeof check for now)\n\t\t\t\tif (typeof message.contents === \"string\") {\n\t\t\t\t\tmessage.contents = JSON.parse(message.contents);\n\t\t\t\t}\n\t\t\t\tconst proposal = message.contents as IProposal;\n\t\t\t\tthis._quorum.addProposal(\n\t\t\t\t\tproposal.key,\n\t\t\t\t\tproposal.value,\n\t\t\t\t\tmessage.sequenceNumber,\n\t\t\t\t\tlocal,\n\t\t\t\t\tmessage.clientSequenceNumber,\n\t\t\t\t);\n\n\t\t\t\t// On a quorum proposal, immediately send a response to expedite the approval.\n\t\t\t\timmediateNoOp = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t}\n\n\t\t// Notify the quorum of the MSN from the message. We rely on it to handle duplicate values but may\n\t\t// want to move that logic to this class.\n\t\tthis._quorum.updateMinimumSequenceNumber(message);\n\n\t\treturn { immediateNoOp };\n\t}\n\n\t/**\n\t * Gets the scribe protocol state\n\t * @param scrubUserData - whether to remove all user data from the quorum members. CAUTION: this will corrupt the quorum if used in a summary.\n\t */\n\tpublic getProtocolState(scrubUserData = false): IScribeProtocolState {\n\t\t// return a new object every time\n\t\t// this ensures future state changes will not affect outside callers\n\t\tconst snapshot = this._quorum.snapshot();\n\n\t\tif (scrubUserData) {\n\t\t\t// In place, remove any identifying client information\n\t\t\tsnapshot.members = snapshot.members.map(([id, sequencedClient]) => [\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\t...sequencedClient,\n\t\t\t\t\tclient: {\n\t\t\t\t\t\t...sequencedClient.client,\n\t\t\t\t\t\tuser: { id: \"\" },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t]);\n\t\t}\n\n\t\treturn {\n\t\t\tsequenceNumber: this.sequenceNumber,\n\t\t\tminimumSequenceNumber: this.minimumSequenceNumber,\n\t\t\t...snapshot,\n\t\t};\n\t}\n}\n"]}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ISequencedClient } from \"@fluidframework/driver-definitions\";\nimport {\n\ttype ISequencedDocumentMessage,\n\ttype IDocumentAttributes,\n\ttype IClientJoin,\n\ttype ICommittedProposal,\n\ttype IProcessMessageResult,\n\ttype IProposal,\n\ttype IQuorum,\n\ttype ISequencedDocumentSystemMessage,\n\ttype ISequencedProposal,\n\tMessageType,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport { type IQuorumSnapshot, Quorum } from \"./quorum.js\";\n\n/**\n * @legacy @beta\n */\nexport interface IScribeProtocolState {\n\tsequenceNumber: number;\n\tminimumSequenceNumber: number;\n\tmembers: [string, ISequencedClient][];\n\tproposals: [number, ISequencedProposal, string[]][];\n\tvalues: [string, ICommittedProposal][];\n}\n\n/**\n * @legacy @beta\n */\nexport interface IProtocolHandler {\n\treadonly quorum: IQuorum;\n\treadonly attributes: IDocumentAttributes;\n\n\tsetConnectionState(connected: boolean, clientId: string | undefined);\n\tsnapshot(): IQuorumSnapshot;\n\n\tclose(): void;\n\tprocessMessage(message: ISequencedDocumentMessage, local: boolean): IProcessMessageResult;\n\tgetProtocolState(): IScribeProtocolState;\n}\n\n/**\n * Handles protocol specific ops.\n * @internal\n */\nexport class ProtocolOpHandler implements IProtocolHandler {\n\tprivate readonly _quorum: Quorum;\n\tpublic get quorum(): Quorum {\n\t\treturn this._quorum;\n\t}\n\n\tconstructor(\n\t\tpublic minimumSequenceNumber: number,\n\t\tpublic sequenceNumber: number,\n\t\tmembers: [string, ISequencedClient][],\n\t\tproposals: [number, ISequencedProposal, string[]][],\n\t\tvalues: [string, ICommittedProposal][],\n\t\tsendProposal: (key: string, value: unknown) => number,\n\t) {\n\t\tthis._quorum = new Quorum(members, proposals, values, sendProposal);\n\t}\n\n\tpublic get attributes(): IDocumentAttributes {\n\t\treturn {\n\t\t\tminimumSequenceNumber: this.minimumSequenceNumber,\n\t\t\tsequenceNumber: this.sequenceNumber,\n\t\t};\n\t}\n\n\tsetConnectionState(connected: boolean, clientId: string | undefined): void {\n\t\tthis._quorum.setConnectionState(connected, clientId);\n\t}\n\n\tsnapshot(): IQuorumSnapshot {\n\t\treturn this._quorum.snapshot();\n\t}\n\n\tpublic close(): void {\n\t\tthis._quorum.close();\n\t}\n\n\tpublic processMessage(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tlocal: boolean,\n\t): IProcessMessageResult {\n\t\t// verify it's moving sequentially\n\t\tif (message.sequenceNumber !== this.sequenceNumber + 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`Protocol state is not moving sequentially. ` +\n\t\t\t\t\t`Current is ${this.sequenceNumber}. Next is ${message.sequenceNumber}`,\n\t\t\t);\n\t\t}\n\n\t\t// Update tracked sequence numbers\n\t\tthis.sequenceNumber = message.sequenceNumber;\n\t\tthis.minimumSequenceNumber = message.minimumSequenceNumber;\n\n\t\tlet immediateNoOp = false;\n\n\t\tswitch (message.type) {\n\t\t\tcase MessageType.ClientJoin: {\n\t\t\t\tconst systemJoinMessage = message as ISequencedDocumentSystemMessage;\n\t\t\t\tconst join = JSON.parse(systemJoinMessage.data) as IClientJoin;\n\t\t\t\tconst member: ISequencedClient = {\n\t\t\t\t\tclient: join.detail,\n\t\t\t\t\tsequenceNumber: systemJoinMessage.sequenceNumber,\n\t\t\t\t};\n\t\t\t\tthis._quorum.addMember(join.clientId, member);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MessageType.ClientLeave: {\n\t\t\t\tconst systemLeaveMessage = message as ISequencedDocumentSystemMessage;\n\t\t\t\tconst clientId = JSON.parse(systemLeaveMessage.data) as string;\n\t\t\t\tthis._quorum.removeMember(clientId);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MessageType.Propose: {\n\t\t\t\t// DeltaManager is no longer parsing the contents, so we expect this to be a string here\n\t\t\t\t// (However, switching this to an assert caused some tests to fail, apparently due to the same\n\t\t\t\t// message object being processed multiple times - So we will retain the typeof check for now)\n\t\t\t\tif (typeof message.contents === \"string\") {\n\t\t\t\t\tmessage.contents = JSON.parse(message.contents);\n\t\t\t\t}\n\t\t\t\tconst proposal = message.contents as IProposal;\n\t\t\t\tthis._quorum.addProposal(\n\t\t\t\t\tproposal.key,\n\t\t\t\t\tproposal.value,\n\t\t\t\t\tmessage.sequenceNumber,\n\t\t\t\t\tlocal,\n\t\t\t\t\tmessage.clientSequenceNumber,\n\t\t\t\t);\n\n\t\t\t\t// On a quorum proposal, immediately send a response to expedite the approval.\n\t\t\t\timmediateNoOp = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t}\n\n\t\t// Notify the quorum of the MSN from the message. We rely on it to handle duplicate values but may\n\t\t// want to move that logic to this class.\n\t\tthis._quorum.updateMinimumSequenceNumber(message);\n\n\t\treturn { immediateNoOp };\n\t}\n\n\t/**\n\t * Gets the scribe protocol state\n\t * @param scrubUserData - whether to remove all user data from the quorum members. CAUTION: this will corrupt the quorum if used in a summary.\n\t */\n\tpublic getProtocolState(scrubUserData = false): IScribeProtocolState {\n\t\t// return a new object every time\n\t\t// this ensures future state changes will not affect outside callers\n\t\tconst snapshot = this._quorum.snapshot();\n\n\t\tif (scrubUserData) {\n\t\t\t// In place, remove any identifying client information\n\t\t\tsnapshot.members = snapshot.members.map(([id, sequencedClient]) => [\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\t...sequencedClient,\n\t\t\t\t\tclient: {\n\t\t\t\t\t\t...sequencedClient.client,\n\t\t\t\t\t\tuser: { id: \"\" },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t]);\n\t\t}\n\n\t\treturn {\n\t\t\tsequenceNumber: this.sequenceNumber,\n\t\t\tminimumSequenceNumber: this.minimumSequenceNumber,\n\t\t\t...snapshot,\n\t\t};\n\t}\n}\n"]}
@@ -10,6 +10,10 @@ const { EventEmitter } = events_pkg;
10
10
  * Structure for tracking proposals that have been sequenced but not approved yet.
11
11
  */
12
12
  class PendingProposal {
13
+ sequenceNumber;
14
+ key;
15
+ value;
16
+ local;
13
17
  constructor(sequenceNumber, key, value, local) {
14
18
  this.sequenceNumber = sequenceNumber;
15
19
  this.key = key;
@@ -22,12 +26,18 @@ class PendingProposal {
22
26
  * @internal
23
27
  */
24
28
  export class QuorumClients extends TypedEventEmitter {
29
+ members;
30
+ isDisposed = false;
25
31
  get disposed() {
26
32
  return this.isDisposed;
27
33
  }
34
+ /**
35
+ * Cached snapshot state, to avoid unnecessary deep clones on repeated snapshot calls.
36
+ * Cleared immediately (set to undefined) when the cache becomes invalid.
37
+ */
38
+ snapshotCache;
28
39
  constructor(snapshot) {
29
40
  super();
30
- this.isDisposed = false;
31
41
  this.members = new Map(snapshot);
32
42
  this.snapshotCache = snapshot;
33
43
  }
@@ -83,15 +93,24 @@ export class QuorumClients extends TypedEventEmitter {
83
93
  * @internal
84
94
  */
85
95
  export class QuorumProposals extends TypedEventEmitter {
96
+ sendProposal;
97
+ proposals;
98
+ values;
99
+ isDisposed = false;
86
100
  get disposed() {
87
101
  return this.isDisposed;
88
102
  }
103
+ // Event emitter for changes to the environment that affect pending proposal promises.
104
+ stateEvents = new EventEmitter();
105
+ /**
106
+ * Cached snapshot state, to avoid unnecessary deep clones on repeated snapshot calls.
107
+ * Cleared immediately (set to undefined) when the cache becomes invalid.
108
+ */
109
+ proposalsSnapshotCache;
110
+ valuesSnapshotCache;
89
111
  constructor(snapshot, sendProposal) {
90
112
  super();
91
113
  this.sendProposal = sendProposal;
92
- this.isDisposed = false;
93
- // Event emitter for changes to the environment that affect pending proposal promises.
94
- this.stateEvents = new EventEmitter();
95
114
  this.proposals = new Map(snapshot.proposals.map(([, proposal]) => {
96
115
  return [
97
116
  proposal.sequenceNumber,
@@ -293,12 +312,14 @@ export class QuorumProposals extends TypedEventEmitter {
293
312
  * @internal
294
313
  */
295
314
  export class Quorum extends TypedEventEmitter {
315
+ quorumClients;
316
+ quorumProposals;
317
+ isDisposed = false;
296
318
  get disposed() {
297
319
  return this.isDisposed;
298
320
  }
299
321
  constructor(members, proposals, values, sendProposal) {
300
322
  super();
301
- this.isDisposed = false;
302
323
  this.quorumClients = new QuorumClients(members);
303
324
  this.quorumClients.on("addMember", (clientId, details) => {
304
325
  this.emit("addMember", clientId, details);
@@ -1 +1 @@
1
- {"version":3,"file":"quorum.js","sourceRoot":"","sources":["../../src/protocol/quorum.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { TypedEventEmitter } from \"@fluid-internal/client-utils\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { IQuorumClients, ISequencedClient } from \"@fluidframework/driver-definitions\";\nimport type {\n\tISequencedDocumentMessage,\n\tICommittedProposal,\n\tIQuorum,\n\tIQuorumProposals,\n\tISequencedProposal,\n} from \"@fluidframework/driver-definitions/internal\";\nimport events_pkg from \"events_pkg\";\nconst { EventEmitter } = events_pkg;\n\n/**\n * Structure for tracking proposals that have been sequenced but not approved yet.\n */\nclass PendingProposal implements ISequencedProposal {\n\tconstructor(\n\t\tpublic readonly sequenceNumber: number,\n\t\tpublic readonly key: string,\n\t\tpublic readonly value: unknown,\n\t\tpublic readonly local: boolean,\n\t) {}\n}\n\n/**\n * Snapshot format for a QuorumClients\n * @legacy @beta\n */\nexport type QuorumClientsSnapshot = [string, ISequencedClient][];\n\n/**\n * Snapshot format for a QuorumProposals\n * @legacy @beta\n */\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport type QuorumProposalsSnapshot = {\n\tproposals: [number, ISequencedProposal, string[]][];\n\tvalues: [string, ICommittedProposal][];\n};\n\n/**\n * Snapshot format for a Quorum\n * @legacy @beta\n */\nexport interface IQuorumSnapshot {\n\tmembers: QuorumClientsSnapshot;\n\tproposals: QuorumProposalsSnapshot[\"proposals\"];\n\tvalues: QuorumProposalsSnapshot[\"values\"];\n}\n\n/**\n * The QuorumClients is used to track members joining and leaving the collaboration session.\n * @internal\n */\nexport class QuorumClients\n\textends TypedEventEmitter<IQuorumClients[\"on\"]>\n\timplements IQuorumClients\n{\n\tprivate readonly members: Map<string, ISequencedClient>;\n\tprivate isDisposed: boolean = false;\n\tpublic get disposed(): boolean {\n\t\treturn this.isDisposed;\n\t}\n\n\t/**\n\t * Cached snapshot state, to avoid unnecessary deep clones on repeated snapshot calls.\n\t * Cleared immediately (set to undefined) when the cache becomes invalid.\n\t */\n\tprivate snapshotCache: QuorumClientsSnapshot | undefined;\n\n\tconstructor(snapshot: QuorumClientsSnapshot) {\n\t\tsuper();\n\n\t\tthis.members = new Map(snapshot);\n\t\tthis.snapshotCache = snapshot;\n\t}\n\n\t/**\n\t * Snapshots the current state of the QuorumClients\n\t * @returns a snapshot of the clients in the quorum\n\t */\n\tpublic snapshot(): QuorumClientsSnapshot {\n\t\tthis.snapshotCache ??= [...this.members];\n\n\t\treturn this.snapshotCache;\n\t}\n\n\t/**\n\t * Adds a new client to the quorum\n\t */\n\tpublic addMember(clientId: string, details: ISequencedClient): void {\n\t\tassert(!!clientId, 0x9a0 /* clientId has to be non-empty string */);\n\t\tassert(!this.members.has(clientId), 0x9a1 /* clientId not found */);\n\t\tthis.members.set(clientId, details);\n\t\tthis.emit(\"addMember\", clientId, details);\n\n\t\t// clear the cache\n\t\tthis.snapshotCache = undefined;\n\t}\n\n\t/**\n\t * Removes a client from the quorum\n\t */\n\tpublic removeMember(clientId: string): void {\n\t\tassert(!!clientId, 0x9a2 /* clientId has to be non-empty string */);\n\t\tassert(this.members.has(clientId), 0x9a3 /* clientId not found */);\n\t\tthis.members.delete(clientId);\n\t\tthis.emit(\"removeMember\", clientId);\n\n\t\t// clear the cache\n\t\tthis.snapshotCache = undefined;\n\t}\n\n\t/**\n\t * Retrieves all the members in the quorum\n\t */\n\tpublic getMembers(): Map<string, ISequencedClient> {\n\t\treturn new Map(this.members);\n\t}\n\n\t/**\n\t * Retrieves a specific member of the quorum\n\t */\n\tpublic getMember(clientId: string): ISequencedClient | undefined {\n\t\treturn this.members.get(clientId);\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.isDisposed = true;\n\t}\n}\n\n/**\n * The QuorumProposals holds a key/value store. Proposed values become finalized in the store once all connected\n * clients have seen the proposal.\n * @internal\n */\nexport class QuorumProposals\n\textends TypedEventEmitter<IQuorumProposals[\"on\"]>\n\timplements IQuorumProposals\n{\n\tprivate readonly proposals: Map<number, PendingProposal>;\n\tprivate readonly values: Map<string, ICommittedProposal>;\n\tprivate isDisposed: boolean = false;\n\tpublic get disposed(): boolean {\n\t\treturn this.isDisposed;\n\t}\n\n\t// Event emitter for changes to the environment that affect pending proposal promises.\n\tprivate readonly stateEvents = new EventEmitter();\n\n\t/**\n\t * Cached snapshot state, to avoid unnecessary deep clones on repeated snapshot calls.\n\t * Cleared immediately (set to undefined) when the cache becomes invalid.\n\t */\n\tprivate proposalsSnapshotCache: QuorumProposalsSnapshot[\"proposals\"] | undefined;\n\tprivate valuesSnapshotCache: QuorumProposalsSnapshot[\"values\"] | undefined;\n\n\tconstructor(\n\t\tsnapshot: QuorumProposalsSnapshot,\n\t\tprivate readonly sendProposal: (key: string, value: unknown) => number,\n\t) {\n\t\tsuper();\n\n\t\tthis.proposals = new Map(\n\t\t\tsnapshot.proposals.map(([, proposal]) => {\n\t\t\t\treturn [\n\t\t\t\t\tproposal.sequenceNumber,\n\t\t\t\t\tnew PendingProposal(\n\t\t\t\t\t\tproposal.sequenceNumber,\n\t\t\t\t\t\tproposal.key,\n\t\t\t\t\t\tproposal.value,\n\t\t\t\t\t\tfalse, // local\n\t\t\t\t\t),\n\t\t\t\t] as [number, PendingProposal];\n\t\t\t}),\n\t\t);\n\t\tthis.values = new Map(snapshot.values);\n\t\tthis.proposalsSnapshotCache = snapshot.proposals;\n\t\tthis.valuesSnapshotCache = snapshot.values;\n\t}\n\n\t/**\n\t * Snapshots the current state of the QuorumProposals\n\t * @returns arrays of proposals and values\n\t */\n\tpublic snapshot(): QuorumProposalsSnapshot {\n\t\tthis.proposalsSnapshotCache ??= [...this.proposals].map(([sequenceNumber, proposal]) => [\n\t\t\tsequenceNumber,\n\t\t\t{ sequenceNumber, key: proposal.key, value: proposal.value },\n\t\t\t[], // rejections, which has been removed\n\t\t]);\n\t\tthis.valuesSnapshotCache ??= [...this.values];\n\n\t\treturn {\n\t\t\tproposals: this.proposalsSnapshotCache,\n\t\t\tvalues: this.valuesSnapshotCache,\n\t\t};\n\t}\n\n\t/**\n\t * Returns whether the quorum has achieved a consensus for the given key.\n\t */\n\tpublic has(key: string): boolean {\n\t\treturn this.values.has(key);\n\t}\n\n\t/**\n\t * Returns the consensus value for the given key\n\t */\n\tpublic get(key: string): unknown {\n\t\treturn this.values.get(key)?.value;\n\t}\n\n\t/**\n\t * Proposes a new value. Returns a promise that will either:\n\t * - Resolve when the proposal is accepted\n\t * - Reject if the proposal fails to send or if the QuorumProposals is disposed\n\t */\n\tpublic async propose(key: string, value: unknown): Promise<void> {\n\t\tconst clientSequenceNumber = this.sendProposal(key, value);\n\t\tif (clientSequenceNumber < 0) {\n\t\t\tthis.emit(\"error\", { eventName: \"ProposalInDisconnectedState\", key });\n\t\t\tthrow new Error(\"Can't propose in disconnected state\");\n\t\t}\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t// The sequence number that our proposal was assigned and went pending.\n\t\t\t// If undefined, then it's not sequenced yet.\n\t\t\tlet thisProposalSequenceNumber: number | undefined;\n\n\t\t\t// A proposal goes through two phases before this promise resolves:\n\t\t\t// 1. Sequencing - waiting for the proposal to be ack'd by the server.\n\t\t\t// 2. Approval - waiting for the proposal to be approved by connected clients.\n\t\t\tconst localProposalSequencedHandler = (\n\t\t\t\tsequencedCSN: number,\n\t\t\t\tsequenceNumber: number,\n\t\t\t): void => {\n\t\t\t\tif (sequencedCSN === clientSequenceNumber) {\n\t\t\t\t\tthisProposalSequenceNumber = sequenceNumber;\n\t\t\t\t\tthis.stateEvents.off(\"localProposalSequenced\", localProposalSequencedHandler);\n\t\t\t\t\tthis.stateEvents.off(\"disconnected\", disconnectedHandler);\n\t\t\t\t\tthis.stateEvents.on(\"localProposalApproved\", localProposalApprovedHandler);\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst localProposalApprovedHandler = (sequenceNumber: number): void => {\n\t\t\t\t// Proposals can be uniquely identified by the sequenceNumber they were assigned.\n\t\t\t\tif (sequenceNumber === thisProposalSequenceNumber) {\n\t\t\t\t\tresolve();\n\t\t\t\t\tremoveListeners();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// There are two error flows we consider: disconnect and disposal.\n\t\t\t// If we get disconnected before the proposal is sequenced, it has one of two possible futures:\n\t\t\t// 1. We reconnect and see the proposal was sequenced in the meantime.\n\t\t\t// -> The promise can still resolve, once it is approved.\n\t\t\t// 2. We reconnect and see the proposal was not sequenced in the meantime, so it will never sequence.\n\t\t\t// -> The promise rejects.\n\t\t\tconst disconnectedHandler = (): void => {\n\t\t\t\t// If we haven't seen the ack by the time we disconnect, we hope to see it by the time we reconnect.\n\t\t\t\tif (thisProposalSequenceNumber === undefined) {\n\t\t\t\t\tthis.stateEvents.once(\"connected\", () => {\n\t\t\t\t\t\t// If we don't see the ack by the time reconnection finishes, it failed to send.\n\t\t\t\t\t\tif (thisProposalSequenceNumber === undefined) {\n\t\t\t\t\t\t\treject(new Error(\"Client disconnected without successfully sending proposal\"));\n\t\t\t\t\t\t\tremoveListeners();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\t\t\t// If the QuorumProposals is disposed of, we assume something catastrophic has happened\n\t\t\t// All outstanding proposals are considered rejected.\n\t\t\tconst disposedHandler = (): void => {\n\t\t\t\treject(new Error(\"QuorumProposals was disposed\"));\n\t\t\t\tremoveListeners();\n\t\t\t};\n\t\t\t// Convenience function to clean up our listeners.\n\t\t\tconst removeListeners = (): void => {\n\t\t\t\tthis.stateEvents.off(\"localProposalSequenced\", localProposalSequencedHandler);\n\t\t\t\tthis.stateEvents.off(\"localProposalApproved\", localProposalApprovedHandler);\n\t\t\t\tthis.stateEvents.off(\"disconnected\", disconnectedHandler);\n\t\t\t\tthis.stateEvents.off(\"disposed\", disposedHandler);\n\t\t\t};\n\t\t\tthis.stateEvents.on(\"localProposalSequenced\", localProposalSequencedHandler);\n\t\t\tthis.stateEvents.on(\"disconnected\", disconnectedHandler);\n\t\t\tthis.stateEvents.on(\"disposed\", disposedHandler);\n\t\t});\n\t}\n\n\t/**\n\t * Begins tracking a new proposal\n\t */\n\tpublic addProposal(\n\t\tkey: string,\n\t\tvalue: unknown,\n\t\tsequenceNumber: number,\n\t\tlocal: boolean,\n\t\tclientSequenceNumber: number,\n\t): void {\n\t\tassert(!this.proposals.has(sequenceNumber), 0x9a4 /* sequenceNumber not found */);\n\n\t\tconst proposal = new PendingProposal(sequenceNumber, key, value, local);\n\t\tthis.proposals.set(sequenceNumber, proposal);\n\n\t\t// Legacy event, from rejection support. May still have some use for clients to learn that a proposal is\n\t\t// likely to be approved soon.\n\t\tthis.emit(\"addProposal\", proposal);\n\n\t\tif (local) {\n\t\t\tthis.stateEvents.emit(\"localProposalSequenced\", clientSequenceNumber, sequenceNumber);\n\t\t}\n\n\t\t// clear the proposal cache\n\t\tthis.proposalsSnapshotCache = undefined;\n\t}\n\n\t/**\n\t * Updates the minimum sequence number. If the MSN advances past the sequence number for any proposal then it\n\t * becomes an approved value.\n\t */\n\tpublic updateMinimumSequenceNumber(message: ISequencedDocumentMessage): void {\n\t\tconst msn = message.minimumSequenceNumber;\n\n\t\t// Accept proposals proposals whose sequenceNumber is <= the minimumSequenceNumber\n\n\t\t// Return a sorted list of approved proposals. We sort so that we apply them in their sequence number order\n\t\t// TODO this can be optimized if necessary to avoid the linear search+sort\n\t\tconst completed: PendingProposal[] = [];\n\t\tfor (const [sequenceNumber, proposal] of this.proposals) {\n\t\t\tif (sequenceNumber <= msn) {\n\t\t\t\tcompleted.push(proposal);\n\t\t\t}\n\t\t}\n\t\tcompleted.sort((a, b) => a.sequenceNumber - b.sequenceNumber);\n\n\t\tfor (const proposal of completed) {\n\t\t\tconst committedProposal: ICommittedProposal = {\n\t\t\t\tapprovalSequenceNumber: message.sequenceNumber,\n\t\t\t\t// No longer used. We still stamp a -1 for compat with older versions of the quorum.\n\t\t\t\t// Can be removed after 0.1035 and higher is ubiquitous.\n\t\t\t\tcommitSequenceNumber: -1,\n\t\t\t\tkey: proposal.key,\n\t\t\t\tsequenceNumber: proposal.sequenceNumber,\n\t\t\t\tvalue: proposal.value,\n\t\t\t};\n\n\t\t\tthis.values.set(committedProposal.key, committedProposal);\n\n\t\t\t// clear the values cache\n\t\t\tthis.valuesSnapshotCache = undefined;\n\n\t\t\t// check if there are multiple proposals with matching keys\n\t\t\tlet proposalSettled = false;\n\t\t\tlet proposalKeySeen = false;\n\t\t\tfor (const [, p] of this.proposals) {\n\t\t\t\tif (p.key === committedProposal.key) {\n\t\t\t\t\tif (proposalKeySeen) {\n\t\t\t\t\t\t// set proposalSettled to false if matching proposal key is not unique\n\t\t\t\t\t\tproposalSettled = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// set proposalSettled to true if the proposal key match is unique thus far\n\t\t\t\t\t\tproposalSettled = true;\n\t\t\t\t\t}\n\t\t\t\t\tproposalKeySeen = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.emit(\n\t\t\t\t\"approveProposal\",\n\t\t\t\tcommittedProposal.sequenceNumber,\n\t\t\t\tcommittedProposal.key,\n\t\t\t\tcommittedProposal.value,\n\t\t\t\tcommittedProposal.approvalSequenceNumber,\n\t\t\t);\n\n\t\t\t// emit approveProposalComplete when all pending proposals are processed\n\t\t\tif (proposalSettled) {\n\t\t\t\tthis.emit(\n\t\t\t\t\t\"approveProposalComplete\",\n\t\t\t\t\tcommittedProposal.sequenceNumber,\n\t\t\t\t\tcommittedProposal.key,\n\t\t\t\t\tcommittedProposal.value,\n\t\t\t\t\tcommittedProposal.approvalSequenceNumber,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.proposals.delete(proposal.sequenceNumber);\n\n\t\t\t// clear the proposals cache\n\t\t\tthis.proposalsSnapshotCache = undefined;\n\t\t\tif (proposal.local) {\n\t\t\t\tthis.stateEvents.emit(\"localProposalApproved\", proposal.sequenceNumber);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic setConnectionState(connected: boolean): void {\n\t\tif (connected) {\n\t\t\tthis.stateEvents.emit(\"connected\");\n\t\t} else {\n\t\t\tthis.stateEvents.emit(\"disconnected\");\n\t\t}\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.isDisposed = true;\n\t\tthis.stateEvents.emit(\"disposed\");\n\t}\n}\n\n/**\n * A quorum represents all clients currently within the collaboration window. As well as the values\n * they have agreed upon and any pending proposals.\n * @internal\n */\nexport class Quorum extends TypedEventEmitter<IQuorum[\"on\"]> implements IQuorum {\n\tprivate readonly quorumClients: QuorumClients;\n\tprivate readonly quorumProposals: QuorumProposals;\n\tprivate readonly isDisposed: boolean = false;\n\tpublic get disposed(): boolean {\n\t\treturn this.isDisposed;\n\t}\n\n\tconstructor(\n\t\tmembers: QuorumClientsSnapshot,\n\t\tproposals: QuorumProposalsSnapshot[\"proposals\"],\n\t\tvalues: QuorumProposalsSnapshot[\"values\"],\n\t\tsendProposal: (key: string, value: unknown) => number,\n\t) {\n\t\tsuper();\n\n\t\tthis.quorumClients = new QuorumClients(members);\n\t\tthis.quorumClients.on(\"addMember\", (clientId: string, details: ISequencedClient) => {\n\t\t\tthis.emit(\"addMember\", clientId, details);\n\t\t});\n\t\tthis.quorumClients.on(\"removeMember\", (clientId: string) => {\n\t\t\tthis.emit(\"removeMember\", clientId);\n\t\t});\n\n\t\tthis.quorumProposals = new QuorumProposals({ proposals, values }, sendProposal);\n\t\tthis.quorumProposals.on(\"addProposal\", (proposal: ISequencedProposal) => {\n\t\t\tthis.emit(\"addProposal\", proposal);\n\t\t});\n\t\tthis.quorumProposals.on(\n\t\t\t\"approveProposal\",\n\t\t\t(\n\t\t\t\tsequenceNumber: number,\n\t\t\t\tkey: string,\n\t\t\t\tvalue: unknown,\n\t\t\t\tapprovalSequenceNumber: number,\n\t\t\t) => {\n\t\t\t\tthis.emit(\"approveProposal\", sequenceNumber, key, value, approvalSequenceNumber);\n\t\t\t},\n\t\t);\n\t}\n\n\tpublic close(): void {\n\t\tthis.removeAllListeners();\n\t}\n\n\t/**\n\t * Snapshots the entire quorum\n\t * @returns a quorum snapshot\n\t */\n\tpublic snapshot(): IQuorumSnapshot {\n\t\tconst members = this.quorumClients.snapshot();\n\t\tconst { proposals, values } = this.quorumProposals.snapshot();\n\t\treturn {\n\t\t\tmembers,\n\t\t\tproposals,\n\t\t\tvalues,\n\t\t};\n\t}\n\n\t/**\n\t * Returns whether the quorum has achieved a consensus for the given key.\n\t */\n\tpublic has(key: string): boolean {\n\t\treturn this.quorumProposals.has(key);\n\t}\n\n\t/**\n\t * Returns the consensus value for the given key\n\t */\n\tpublic get(key: string): unknown {\n\t\treturn this.quorumProposals.get(key);\n\t}\n\n\t/**\n\t * Adds a new client to the quorum\n\t */\n\tpublic addMember(clientId: string, details: ISequencedClient): void {\n\t\tthis.quorumClients.addMember(clientId, details);\n\t}\n\n\t/**\n\t * Removes a client from the quorum\n\t */\n\tpublic removeMember(clientId: string): void {\n\t\tthis.quorumClients.removeMember(clientId);\n\t}\n\n\t/**\n\t * Retrieves all the members in the quorum\n\t */\n\tpublic getMembers(): Map<string, ISequencedClient> {\n\t\treturn this.quorumClients.getMembers();\n\t}\n\n\t/**\n\t * Retrieves a specific member of the quorum\n\t */\n\tpublic getMember(clientId: string): ISequencedClient | undefined {\n\t\treturn this.quorumClients.getMember(clientId);\n\t}\n\n\t/**\n\t * Proposes a new value. Returns a promise that will resolve when the proposal is either accepted, or reject if\n\t * the proposal fails to send.\n\t */\n\tpublic async propose(key: string, value: unknown): Promise<void> {\n\t\treturn this.quorumProposals.propose(key, value);\n\t}\n\n\t/**\n\t * Begins tracking a new proposal\n\t */\n\tpublic addProposal(\n\t\tkey: string,\n\t\tvalue: unknown,\n\t\tsequenceNumber: number,\n\t\tlocal: boolean,\n\t\tclientSequenceNumber: number,\n\t): void {\n\t\treturn this.quorumProposals.addProposal(\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\tsequenceNumber,\n\t\t\tlocal,\n\t\t\tclientSequenceNumber,\n\t\t);\n\t}\n\n\t/**\n\t * Updates the minimum sequence number. If the MSN advances past the sequence number for any proposal then it\n\t * becomes an approved value.\n\t */\n\tpublic updateMinimumSequenceNumber(message: ISequencedDocumentMessage): void {\n\t\tthis.quorumProposals.updateMinimumSequenceNumber(message);\n\t}\n\n\tpublic setConnectionState(connected: boolean, clientId?: string): void {\n\t\tthis.quorumProposals.setConnectionState(connected);\n\t}\n\n\tpublic dispose(): void {\n\t\tthrow new Error(\"Not implemented.\");\n\t}\n}\n"]}
1
+ {"version":3,"file":"quorum.js","sourceRoot":"","sources":["../../src/protocol/quorum.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { TypedEventEmitter } from \"@fluid-internal/client-utils\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { IQuorumClients, ISequencedClient } from \"@fluidframework/driver-definitions\";\nimport type {\n\tISequencedDocumentMessage,\n\tICommittedProposal,\n\tIQuorum,\n\tIQuorumProposals,\n\tISequencedProposal,\n} from \"@fluidframework/driver-definitions/internal\";\nimport events_pkg from \"events_pkg\";\nconst { EventEmitter } = events_pkg;\n\n/**\n * Structure for tracking proposals that have been sequenced but not approved yet.\n */\nclass PendingProposal implements ISequencedProposal {\n\tconstructor(\n\t\tpublic readonly sequenceNumber: number,\n\t\tpublic readonly key: string,\n\t\tpublic readonly value: unknown,\n\t\tpublic readonly local: boolean,\n\t) {}\n}\n\n/**\n * Snapshot format for a QuorumClients\n * @legacy @beta\n */\nexport type QuorumClientsSnapshot = [string, ISequencedClient][];\n\n/**\n * Snapshot format for a QuorumProposals\n * @legacy @beta\n */\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport type QuorumProposalsSnapshot = {\n\tproposals: [number, ISequencedProposal, string[]][];\n\tvalues: [string, ICommittedProposal][];\n};\n\n/**\n * Snapshot format for a Quorum\n * @legacy @beta\n */\nexport interface IQuorumSnapshot {\n\tmembers: QuorumClientsSnapshot;\n\tproposals: QuorumProposalsSnapshot[\"proposals\"];\n\tvalues: QuorumProposalsSnapshot[\"values\"];\n}\n\n/**\n * The QuorumClients is used to track members joining and leaving the collaboration session.\n * @internal\n */\nexport class QuorumClients\n\textends TypedEventEmitter<IQuorumClients[\"on\"]>\n\timplements IQuorumClients\n{\n\tprivate readonly members: Map<string, ISequencedClient>;\n\tprivate isDisposed: boolean = false;\n\tpublic get disposed(): boolean {\n\t\treturn this.isDisposed;\n\t}\n\n\t/**\n\t * Cached snapshot state, to avoid unnecessary deep clones on repeated snapshot calls.\n\t * Cleared immediately (set to undefined) when the cache becomes invalid.\n\t */\n\tprivate snapshotCache: QuorumClientsSnapshot | undefined;\n\n\tconstructor(snapshot: QuorumClientsSnapshot) {\n\t\tsuper();\n\n\t\tthis.members = new Map(snapshot);\n\t\tthis.snapshotCache = snapshot;\n\t}\n\n\t/**\n\t * Snapshots the current state of the QuorumClients\n\t * @returns a snapshot of the clients in the quorum\n\t */\n\tpublic snapshot(): QuorumClientsSnapshot {\n\t\tthis.snapshotCache ??= [...this.members];\n\n\t\treturn this.snapshotCache;\n\t}\n\n\t/**\n\t * Adds a new client to the quorum\n\t */\n\tpublic addMember(clientId: string, details: ISequencedClient): void {\n\t\tassert(!!clientId, 0x9a0 /* clientId has to be non-empty string */);\n\t\tassert(!this.members.has(clientId), 0x9a1 /* clientId not found */);\n\t\tthis.members.set(clientId, details);\n\t\tthis.emit(\"addMember\", clientId, details);\n\n\t\t// clear the cache\n\t\tthis.snapshotCache = undefined;\n\t}\n\n\t/**\n\t * Removes a client from the quorum\n\t */\n\tpublic removeMember(clientId: string): void {\n\t\tassert(!!clientId, 0x9a2 /* clientId has to be non-empty string */);\n\t\tassert(this.members.has(clientId), 0x9a3 /* clientId not found */);\n\t\tthis.members.delete(clientId);\n\t\tthis.emit(\"removeMember\", clientId);\n\n\t\t// clear the cache\n\t\tthis.snapshotCache = undefined;\n\t}\n\n\t/**\n\t * Retrieves all the members in the quorum\n\t */\n\tpublic getMembers(): Map<string, ISequencedClient> {\n\t\treturn new Map(this.members);\n\t}\n\n\t/**\n\t * Retrieves a specific member of the quorum\n\t */\n\tpublic getMember(clientId: string): ISequencedClient | undefined {\n\t\treturn this.members.get(clientId);\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.isDisposed = true;\n\t}\n}\n\n/**\n * The QuorumProposals holds a key/value store. Proposed values become finalized in the store once all connected\n * clients have seen the proposal.\n * @internal\n */\nexport class QuorumProposals\n\textends TypedEventEmitter<IQuorumProposals[\"on\"]>\n\timplements IQuorumProposals\n{\n\tprivate readonly proposals: Map<number, PendingProposal>;\n\tprivate readonly values: Map<string, ICommittedProposal>;\n\tprivate isDisposed: boolean = false;\n\tpublic get disposed(): boolean {\n\t\treturn this.isDisposed;\n\t}\n\n\t// Event emitter for changes to the environment that affect pending proposal promises.\n\tprivate readonly stateEvents = new EventEmitter();\n\n\t/**\n\t * Cached snapshot state, to avoid unnecessary deep clones on repeated snapshot calls.\n\t * Cleared immediately (set to undefined) when the cache becomes invalid.\n\t */\n\tprivate proposalsSnapshotCache: QuorumProposalsSnapshot[\"proposals\"] | undefined;\n\tprivate valuesSnapshotCache: QuorumProposalsSnapshot[\"values\"] | undefined;\n\n\tconstructor(\n\t\tsnapshot: QuorumProposalsSnapshot,\n\t\tprivate readonly sendProposal: (key: string, value: unknown) => number,\n\t) {\n\t\tsuper();\n\n\t\tthis.proposals = new Map(\n\t\t\tsnapshot.proposals.map(([, proposal]) => {\n\t\t\t\treturn [\n\t\t\t\t\tproposal.sequenceNumber,\n\t\t\t\t\tnew PendingProposal(\n\t\t\t\t\t\tproposal.sequenceNumber,\n\t\t\t\t\t\tproposal.key,\n\t\t\t\t\t\tproposal.value,\n\t\t\t\t\t\tfalse, // local\n\t\t\t\t\t),\n\t\t\t\t] as [number, PendingProposal];\n\t\t\t}),\n\t\t);\n\t\tthis.values = new Map(snapshot.values);\n\t\tthis.proposalsSnapshotCache = snapshot.proposals;\n\t\tthis.valuesSnapshotCache = snapshot.values;\n\t}\n\n\t/**\n\t * Snapshots the current state of the QuorumProposals\n\t * @returns arrays of proposals and values\n\t */\n\tpublic snapshot(): QuorumProposalsSnapshot {\n\t\tthis.proposalsSnapshotCache ??= [...this.proposals].map(([sequenceNumber, proposal]) => [\n\t\t\tsequenceNumber,\n\t\t\t{ sequenceNumber, key: proposal.key, value: proposal.value },\n\t\t\t[], // rejections, which has been removed\n\t\t]);\n\t\tthis.valuesSnapshotCache ??= [...this.values];\n\n\t\treturn {\n\t\t\tproposals: this.proposalsSnapshotCache,\n\t\t\tvalues: this.valuesSnapshotCache,\n\t\t};\n\t}\n\n\t/**\n\t * Returns whether the quorum has achieved a consensus for the given key.\n\t */\n\tpublic has(key: string): boolean {\n\t\treturn this.values.has(key);\n\t}\n\n\t/**\n\t * Returns the consensus value for the given key\n\t */\n\tpublic get(key: string): unknown {\n\t\treturn this.values.get(key)?.value;\n\t}\n\n\t/**\n\t * Proposes a new value. Returns a promise that will either:\n\t * - Resolve when the proposal is accepted\n\t * - Reject if the proposal fails to send or if the QuorumProposals is disposed\n\t */\n\tpublic async propose(key: string, value: unknown): Promise<void> {\n\t\tconst clientSequenceNumber = this.sendProposal(key, value);\n\t\tif (clientSequenceNumber < 0) {\n\t\t\tthis.emit(\"error\", { eventName: \"ProposalInDisconnectedState\", key });\n\t\t\tthrow new Error(\"Can't propose in disconnected state\");\n\t\t}\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t// The sequence number that our proposal was assigned and went pending.\n\t\t\t// If undefined, then it's not sequenced yet.\n\t\t\tlet thisProposalSequenceNumber: number | undefined;\n\n\t\t\t// A proposal goes through two phases before this promise resolves:\n\t\t\t// 1. Sequencing - waiting for the proposal to be ack'd by the server.\n\t\t\t// 2. Approval - waiting for the proposal to be approved by connected clients.\n\t\t\tconst localProposalSequencedHandler = (\n\t\t\t\tsequencedCSN: number,\n\t\t\t\tsequenceNumber: number,\n\t\t\t): void => {\n\t\t\t\tif (sequencedCSN === clientSequenceNumber) {\n\t\t\t\t\tthisProposalSequenceNumber = sequenceNumber;\n\t\t\t\t\tthis.stateEvents.off(\"localProposalSequenced\", localProposalSequencedHandler);\n\t\t\t\t\tthis.stateEvents.off(\"disconnected\", disconnectedHandler);\n\t\t\t\t\tthis.stateEvents.on(\"localProposalApproved\", localProposalApprovedHandler);\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst localProposalApprovedHandler = (sequenceNumber: number): void => {\n\t\t\t\t// Proposals can be uniquely identified by the sequenceNumber they were assigned.\n\t\t\t\tif (sequenceNumber === thisProposalSequenceNumber) {\n\t\t\t\t\tresolve();\n\t\t\t\t\tremoveListeners();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// There are two error flows we consider: disconnect and disposal.\n\t\t\t// If we get disconnected before the proposal is sequenced, it has one of two possible futures:\n\t\t\t// 1. We reconnect and see the proposal was sequenced in the meantime.\n\t\t\t// -> The promise can still resolve, once it is approved.\n\t\t\t// 2. We reconnect and see the proposal was not sequenced in the meantime, so it will never sequence.\n\t\t\t// -> The promise rejects.\n\t\t\tconst disconnectedHandler = (): void => {\n\t\t\t\t// If we haven't seen the ack by the time we disconnect, we hope to see it by the time we reconnect.\n\t\t\t\tif (thisProposalSequenceNumber === undefined) {\n\t\t\t\t\tthis.stateEvents.once(\"connected\", () => {\n\t\t\t\t\t\t// If we don't see the ack by the time reconnection finishes, it failed to send.\n\t\t\t\t\t\tif (thisProposalSequenceNumber === undefined) {\n\t\t\t\t\t\t\treject(new Error(\"Client disconnected without successfully sending proposal\"));\n\t\t\t\t\t\t\tremoveListeners();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\t\t\t// If the QuorumProposals is disposed of, we assume something catastrophic has happened\n\t\t\t// All outstanding proposals are considered rejected.\n\t\t\tconst disposedHandler = (): void => {\n\t\t\t\treject(new Error(\"QuorumProposals was disposed\"));\n\t\t\t\tremoveListeners();\n\t\t\t};\n\t\t\t// Convenience function to clean up our listeners.\n\t\t\tconst removeListeners = (): void => {\n\t\t\t\tthis.stateEvents.off(\"localProposalSequenced\", localProposalSequencedHandler);\n\t\t\t\tthis.stateEvents.off(\"localProposalApproved\", localProposalApprovedHandler);\n\t\t\t\tthis.stateEvents.off(\"disconnected\", disconnectedHandler);\n\t\t\t\tthis.stateEvents.off(\"disposed\", disposedHandler);\n\t\t\t};\n\t\t\tthis.stateEvents.on(\"localProposalSequenced\", localProposalSequencedHandler);\n\t\t\tthis.stateEvents.on(\"disconnected\", disconnectedHandler);\n\t\t\tthis.stateEvents.on(\"disposed\", disposedHandler);\n\t\t});\n\t}\n\n\t/**\n\t * Begins tracking a new proposal\n\t */\n\tpublic addProposal(\n\t\tkey: string,\n\t\tvalue: unknown,\n\t\tsequenceNumber: number,\n\t\tlocal: boolean,\n\t\tclientSequenceNumber: number,\n\t): void {\n\t\tassert(!this.proposals.has(sequenceNumber), 0x9a4 /* sequenceNumber not found */);\n\n\t\tconst proposal = new PendingProposal(sequenceNumber, key, value, local);\n\t\tthis.proposals.set(sequenceNumber, proposal);\n\n\t\t// Legacy event, from rejection support. May still have some use for clients to learn that a proposal is\n\t\t// likely to be approved soon.\n\t\tthis.emit(\"addProposal\", proposal);\n\n\t\tif (local) {\n\t\t\tthis.stateEvents.emit(\"localProposalSequenced\", clientSequenceNumber, sequenceNumber);\n\t\t}\n\n\t\t// clear the proposal cache\n\t\tthis.proposalsSnapshotCache = undefined;\n\t}\n\n\t/**\n\t * Updates the minimum sequence number. If the MSN advances past the sequence number for any proposal then it\n\t * becomes an approved value.\n\t */\n\tpublic updateMinimumSequenceNumber(message: ISequencedDocumentMessage): void {\n\t\tconst msn = message.minimumSequenceNumber;\n\n\t\t// Accept proposals proposals whose sequenceNumber is <= the minimumSequenceNumber\n\n\t\t// Return a sorted list of approved proposals. We sort so that we apply them in their sequence number order\n\t\t// TODO this can be optimized if necessary to avoid the linear search+sort\n\t\tconst completed: PendingProposal[] = [];\n\t\tfor (const [sequenceNumber, proposal] of this.proposals) {\n\t\t\tif (sequenceNumber <= msn) {\n\t\t\t\tcompleted.push(proposal);\n\t\t\t}\n\t\t}\n\t\tcompleted.sort((a, b) => a.sequenceNumber - b.sequenceNumber);\n\n\t\tfor (const proposal of completed) {\n\t\t\tconst committedProposal: ICommittedProposal = {\n\t\t\t\tapprovalSequenceNumber: message.sequenceNumber,\n\t\t\t\t// No longer used. We still stamp a -1 for compat with older versions of the quorum.\n\t\t\t\t// Can be removed after 0.1035 and higher is ubiquitous.\n\t\t\t\tcommitSequenceNumber: -1,\n\t\t\t\tkey: proposal.key,\n\t\t\t\tsequenceNumber: proposal.sequenceNumber,\n\t\t\t\tvalue: proposal.value,\n\t\t\t};\n\n\t\t\tthis.values.set(committedProposal.key, committedProposal);\n\n\t\t\t// clear the values cache\n\t\t\tthis.valuesSnapshotCache = undefined;\n\n\t\t\t// check if there are multiple proposals with matching keys\n\t\t\tlet proposalSettled = false;\n\t\t\tlet proposalKeySeen = false;\n\t\t\tfor (const [, p] of this.proposals) {\n\t\t\t\tif (p.key === committedProposal.key) {\n\t\t\t\t\tif (proposalKeySeen) {\n\t\t\t\t\t\t// set proposalSettled to false if matching proposal key is not unique\n\t\t\t\t\t\tproposalSettled = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// set proposalSettled to true if the proposal key match is unique thus far\n\t\t\t\t\t\tproposalSettled = true;\n\t\t\t\t\t}\n\t\t\t\t\tproposalKeySeen = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.emit(\n\t\t\t\t\"approveProposal\",\n\t\t\t\tcommittedProposal.sequenceNumber,\n\t\t\t\tcommittedProposal.key,\n\t\t\t\tcommittedProposal.value,\n\t\t\t\tcommittedProposal.approvalSequenceNumber,\n\t\t\t);\n\n\t\t\t// emit approveProposalComplete when all pending proposals are processed\n\t\t\tif (proposalSettled) {\n\t\t\t\tthis.emit(\n\t\t\t\t\t\"approveProposalComplete\",\n\t\t\t\t\tcommittedProposal.sequenceNumber,\n\t\t\t\t\tcommittedProposal.key,\n\t\t\t\t\tcommittedProposal.value,\n\t\t\t\t\tcommittedProposal.approvalSequenceNumber,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.proposals.delete(proposal.sequenceNumber);\n\n\t\t\t// clear the proposals cache\n\t\t\tthis.proposalsSnapshotCache = undefined;\n\t\t\tif (proposal.local) {\n\t\t\t\tthis.stateEvents.emit(\"localProposalApproved\", proposal.sequenceNumber);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic setConnectionState(connected: boolean): void {\n\t\tif (connected) {\n\t\t\tthis.stateEvents.emit(\"connected\");\n\t\t} else {\n\t\t\tthis.stateEvents.emit(\"disconnected\");\n\t\t}\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.isDisposed = true;\n\t\tthis.stateEvents.emit(\"disposed\");\n\t}\n}\n\n/**\n * A quorum represents all clients currently within the collaboration window. As well as the values\n * they have agreed upon and any pending proposals.\n * @internal\n */\nexport class Quorum extends TypedEventEmitter<IQuorum[\"on\"]> implements IQuorum {\n\tprivate readonly quorumClients: QuorumClients;\n\tprivate readonly quorumProposals: QuorumProposals;\n\tprivate readonly isDisposed: boolean = false;\n\tpublic get disposed(): boolean {\n\t\treturn this.isDisposed;\n\t}\n\n\tconstructor(\n\t\tmembers: QuorumClientsSnapshot,\n\t\tproposals: QuorumProposalsSnapshot[\"proposals\"],\n\t\tvalues: QuorumProposalsSnapshot[\"values\"],\n\t\tsendProposal: (key: string, value: unknown) => number,\n\t) {\n\t\tsuper();\n\n\t\tthis.quorumClients = new QuorumClients(members);\n\t\tthis.quorumClients.on(\"addMember\", (clientId: string, details: ISequencedClient) => {\n\t\t\tthis.emit(\"addMember\", clientId, details);\n\t\t});\n\t\tthis.quorumClients.on(\"removeMember\", (clientId: string) => {\n\t\t\tthis.emit(\"removeMember\", clientId);\n\t\t});\n\n\t\tthis.quorumProposals = new QuorumProposals({ proposals, values }, sendProposal);\n\t\tthis.quorumProposals.on(\"addProposal\", (proposal: ISequencedProposal) => {\n\t\t\tthis.emit(\"addProposal\", proposal);\n\t\t});\n\t\tthis.quorumProposals.on(\n\t\t\t\"approveProposal\",\n\t\t\t(\n\t\t\t\tsequenceNumber: number,\n\t\t\t\tkey: string,\n\t\t\t\tvalue: unknown,\n\t\t\t\tapprovalSequenceNumber: number,\n\t\t\t) => {\n\t\t\t\tthis.emit(\"approveProposal\", sequenceNumber, key, value, approvalSequenceNumber);\n\t\t\t},\n\t\t);\n\t}\n\n\tpublic close(): void {\n\t\tthis.removeAllListeners();\n\t}\n\n\t/**\n\t * Snapshots the entire quorum\n\t * @returns a quorum snapshot\n\t */\n\tpublic snapshot(): IQuorumSnapshot {\n\t\tconst members = this.quorumClients.snapshot();\n\t\tconst { proposals, values } = this.quorumProposals.snapshot();\n\t\treturn {\n\t\t\tmembers,\n\t\t\tproposals,\n\t\t\tvalues,\n\t\t};\n\t}\n\n\t/**\n\t * Returns whether the quorum has achieved a consensus for the given key.\n\t */\n\tpublic has(key: string): boolean {\n\t\treturn this.quorumProposals.has(key);\n\t}\n\n\t/**\n\t * Returns the consensus value for the given key\n\t */\n\tpublic get(key: string): unknown {\n\t\treturn this.quorumProposals.get(key);\n\t}\n\n\t/**\n\t * Adds a new client to the quorum\n\t */\n\tpublic addMember(clientId: string, details: ISequencedClient): void {\n\t\tthis.quorumClients.addMember(clientId, details);\n\t}\n\n\t/**\n\t * Removes a client from the quorum\n\t */\n\tpublic removeMember(clientId: string): void {\n\t\tthis.quorumClients.removeMember(clientId);\n\t}\n\n\t/**\n\t * Retrieves all the members in the quorum\n\t */\n\tpublic getMembers(): Map<string, ISequencedClient> {\n\t\treturn this.quorumClients.getMembers();\n\t}\n\n\t/**\n\t * Retrieves a specific member of the quorum\n\t */\n\tpublic getMember(clientId: string): ISequencedClient | undefined {\n\t\treturn this.quorumClients.getMember(clientId);\n\t}\n\n\t/**\n\t * Proposes a new value. Returns a promise that will resolve when the proposal is either accepted, or reject if\n\t * the proposal fails to send.\n\t */\n\tpublic async propose(key: string, value: unknown): Promise<void> {\n\t\treturn this.quorumProposals.propose(key, value);\n\t}\n\n\t/**\n\t * Begins tracking a new proposal\n\t */\n\tpublic addProposal(\n\t\tkey: string,\n\t\tvalue: unknown,\n\t\tsequenceNumber: number,\n\t\tlocal: boolean,\n\t\tclientSequenceNumber: number,\n\t): void {\n\t\treturn this.quorumProposals.addProposal(\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\tsequenceNumber,\n\t\t\tlocal,\n\t\t\tclientSequenceNumber,\n\t\t);\n\t}\n\n\t/**\n\t * Updates the minimum sequence number. If the MSN advances past the sequence number for any proposal then it\n\t * becomes an approved value.\n\t */\n\tpublic updateMinimumSequenceNumber(message: ISequencedDocumentMessage): void {\n\t\tthis.quorumProposals.updateMinimumSequenceNumber(message);\n\t}\n\n\tpublic setConnectionState(connected: boolean, clientId?: string): void {\n\t\tthis.quorumProposals.setConnectionState(connected);\n\t}\n\n\tpublic dispose(): void {\n\t\tthrow new Error(\"Not implemented.\");\n\t}\n}\n"]}
package/lib/protocol.js CHANGED
@@ -12,6 +12,8 @@ export const SignalType = {
12
12
  Clear: "clear", // used only by client for synthetic signals
13
13
  };
14
14
  export class ProtocolHandler extends ProtocolOpHandler {
15
+ audience;
16
+ shouldClientHaveLeft;
15
17
  constructor(attributes, quorumSnapshot,
16
18
  // TODO: use a real type (breaking change)
17
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IAudienceOwner } from \"@fluidframework/container-definitions/internal\";\nimport {\n\ttype IDocumentAttributes,\n\ttype IProcessMessageResult,\n\ttype ISignalClient,\n\tMessageType,\n\ttype ISequencedDocumentMessage,\n\ttype ISignalMessage,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { canBeCoalescedByService } from \"@fluidframework/driver-utils/internal\";\n\nimport {\n\ttype IBaseProtocolHandler,\n\ttype IQuorumSnapshot,\n\tProtocolOpHandler,\n} from \"./protocol/index.js\";\n\n// ADO: #1986: Start using enum from protocol-base.\nexport const SignalType = {\n\tClientJoin: \"join\", // same value as MessageType.ClientJoin,\n\tClientLeave: \"leave\", // same value as MessageType.ClientLeave,\n\tClear: \"clear\", // used only by client for synthetic signals\n} as const;\n\ninterface SystemSignalContent {\n\ttype: (typeof SignalType)[keyof typeof SignalType];\n\tcontent?: unknown;\n}\n\ninterface InboundSystemSignal<TSignalContent extends SystemSignalContent>\n\textends ISignalMessage<{ type: never; content: TSignalContent }> {\n\t// eslint-disable-next-line @rushstack/no-new-null -- `null` is used in JSON protocol to indicate system message\n\treadonly clientId: null;\n}\n\ntype ClientJoinSignal = InboundSystemSignal<{\n\ttype: typeof SignalType.ClientJoin;\n\tcontent: ISignalClient;\n}>;\n\ntype ClientLeaveSignal = InboundSystemSignal<{\n\ttype: typeof SignalType.ClientLeave;\n\tcontent: string; // clientId of leaving client\n}>;\n\ntype ClearClientsSignal = InboundSystemSignal<{\n\ttype: typeof SignalType.Clear;\n}>;\n\ntype AudienceSignal = ClientJoinSignal | ClientLeaveSignal | ClearClientsSignal;\n\n/**\n * Function to be used for creating a protocol handler.\n * @legacy @beta\n */\nexport type ProtocolHandlerBuilder = (\n\tattributes: IDocumentAttributes,\n\tsnapshot: IQuorumSnapshot,\n\t// TODO: use a real type (breaking change)\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tsendProposal: (key: string, value: any) => number,\n) => IProtocolHandler;\n\n/**\n * @legacy @beta\n */\nexport interface IProtocolHandler extends IBaseProtocolHandler {\n\treadonly audience: IAudienceOwner;\n\tprocessSignal(message: ISignalMessage);\n}\n\n/**\n * More specific version of {@link IProtocolHandler} with narrower call\n * constraints for {@link IProtocolHandler.processSignal}.\n */\nexport interface ProtocolHandlerInternal extends IProtocolHandler {\n\t/**\n\t * Process the audience related signal.\n\t * @privateRemarks\n\t * Internally, only {@link AudienceSignal} messages need handling.\n\t */\n\tprocessSignal(message: AudienceSignal): void;\n}\n\n/**\n * Function to be used for creating a protocol handler.\n *\n * @remarks This is the same are {@link ProtocolHandlerBuilder} but\n * returns the {@link ProtocolHandlerInternal} which has narrower\n * expectations for `processSignal`.\n */\nexport type InternalProtocolHandlerBuilder = (\n\tattributes: IDocumentAttributes,\n\tsnapshot: IQuorumSnapshot,\n\t// TODO: use a real type (breaking change)\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tsendProposal: (key: string, value: any) => number,\n) => ProtocolHandlerInternal;\n\nexport class ProtocolHandler extends ProtocolOpHandler implements ProtocolHandlerInternal {\n\tconstructor(\n\t\tattributes: IDocumentAttributes,\n\t\tquorumSnapshot: IQuorumSnapshot,\n\t\t// TODO: use a real type (breaking change)\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tsendProposal: (key: string, value: any) => number,\n\t\tpublic readonly audience: IAudienceOwner,\n\t\tprivate readonly shouldClientHaveLeft: (clientId: string) => boolean,\n\t) {\n\t\tsuper(\n\t\t\tattributes.minimumSequenceNumber,\n\t\t\tattributes.sequenceNumber,\n\t\t\tquorumSnapshot.members,\n\t\t\tquorumSnapshot.proposals,\n\t\t\tquorumSnapshot.values,\n\t\t\tsendProposal,\n\t\t);\n\n\t\tfor (const [clientId, member] of this.quorum.getMembers()) {\n\t\t\taudience.addMember(clientId, member.client);\n\t\t}\n\n\t\t// Join / leave signals are ignored for \"write\" clients in favor of join / leave ops\n\t\tthis.quorum.on(\"addMember\", (clientId, details) =>\n\t\t\taudience.addMember(clientId, details.client),\n\t\t);\n\t\tthis.quorum.on(\"removeMember\", (clientId) => audience.removeMember(clientId));\n\t}\n\n\tpublic processMessage(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tlocal: boolean,\n\t): IProcessMessageResult {\n\t\t// Check and report if we're getting messages from a clientId that we previously\n\t\t// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be\n\t\t// eslint-disable-next-line unicorn/no-null\n\t\tif (message.clientId != null) {\n\t\t\tconst client = this.quorum.getMember(message.clientId);\n\n\t\t\tif (client === undefined && message.type !== MessageType.ClientJoin) {\n\t\t\t\t// pre-0.58 error message: messageClientIdMissingFromQuorum\n\t\t\t\tthrow new Error(\"Remote message's clientId is missing from the quorum\");\n\t\t\t}\n\n\t\t\t// Here checking canBeCoalescedByService is used as an approximation of \"is benign to process despite being unexpected\".\n\t\t\t// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the\n\t\t\t// document we don't need to blow up aggressively.\n\t\t\tif (this.shouldClientHaveLeft(message.clientId) && !canBeCoalescedByService(message)) {\n\t\t\t\t// pre-0.58 error message: messageClientIdShouldHaveLeft\n\t\t\t\tthrow new Error(\"Remote message's clientId already should have left\");\n\t\t\t}\n\t\t}\n\n\t\treturn super.processMessage(message, local);\n\t}\n\n\tpublic processSignal(message: AudienceSignal): void {\n\t\tconst innerContent = message.content;\n\t\tswitch (innerContent.type) {\n\t\t\tcase SignalType.Clear: {\n\t\t\t\tconst members = this.audience.getMembers();\n\t\t\t\tfor (const [clientId, client] of members) {\n\t\t\t\t\tif (client.mode === \"read\") {\n\t\t\t\t\t\tthis.audience.removeMember(clientId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientJoin: {\n\t\t\t\tconst newClient = innerContent.content;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (newClient.client.mode === \"read\") {\n\t\t\t\t\tthis.audience.addMember(newClient.clientId, newClient.client);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientLeave: {\n\t\t\t\tconst leftClientId = innerContent.content;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (this.audience.getMember(leftClientId)?.mode === \"read\") {\n\t\t\t\t\tthis.audience.removeMember(leftClientId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Function to check whether the protocol handler should process the Signal.\n * The protocol handler should strictly handle only ClientJoin, ClientLeave\n * and Clear signal types.\n */\nexport function protocolHandlerShouldProcessSignal(\n\tmessage: ISignalMessage,\n): message is AudienceSignal {\n\t// Signal originates from server\n\tif (message.clientId === null) {\n\t\tconst innerContent = message.content as { content: unknown; type: string };\n\t\treturn (\n\t\t\tinnerContent.type === SignalType.Clear ||\n\t\t\tinnerContent.type === SignalType.ClientJoin ||\n\t\t\tinnerContent.type === SignalType.ClientLeave\n\t\t);\n\t}\n\treturn false;\n}\n\nexport function wrapProtocolHandlerBuilder(\n\tbuilder: ProtocolHandlerBuilder,\n\tsignalAudience: IAudienceOwner,\n): InternalProtocolHandlerBuilder {\n\treturn (\n\t\tattributes: IDocumentAttributes,\n\t\tsnapshot: IQuorumSnapshot,\n\t\tsendProposal: (key: string, value: unknown) => number,\n\t): ProtocolHandlerInternal => {\n\t\tconst baseHandler = builder(attributes, snapshot, sendProposal);\n\t\t// Create proxy handler with an overridden processSignal method.\n\t\t// Use a Proxy since base may use [dynamic] property getters.\n\t\treturn new Proxy(baseHandler, {\n\t\t\tget(target, prop, receiver) {\n\t\t\t\tif (prop === \"processSignal\") {\n\t\t\t\t\treturn (message: AudienceSignal) => {\n\t\t\t\t\t\tconst innerContent = message.content;\n\t\t\t\t\t\tswitch (innerContent.type) {\n\t\t\t\t\t\t\tcase SignalType.Clear: {\n\t\t\t\t\t\t\t\tconst members = signalAudience.getMembers();\n\t\t\t\t\t\t\t\tfor (const clientId of members.keys()) {\n\t\t\t\t\t\t\t\t\tsignalAudience.removeMember(clientId);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase SignalType.ClientJoin: {\n\t\t\t\t\t\t\t\tconst newClient = innerContent.content;\n\t\t\t\t\t\t\t\tsignalAudience.addMember(newClient.clientId, newClient.client);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase SignalType.ClientLeave: {\n\t\t\t\t\t\t\t\tconst leftClientId = innerContent.content;\n\t\t\t\t\t\t\t\tsignalAudience.removeMember(leftClientId);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdefault: {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttarget.processSignal(message);\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\t\t\treturn Reflect.get(target, prop, receiver);\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IAudienceOwner } from \"@fluidframework/container-definitions/internal\";\nimport {\n\ttype IDocumentAttributes,\n\ttype IProcessMessageResult,\n\ttype ISignalClient,\n\tMessageType,\n\ttype ISequencedDocumentMessage,\n\ttype ISignalMessage,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { canBeCoalescedByService } from \"@fluidframework/driver-utils/internal\";\n\nimport {\n\ttype IBaseProtocolHandler,\n\ttype IQuorumSnapshot,\n\tProtocolOpHandler,\n} from \"./protocol/index.js\";\n\n// ADO: #1986: Start using enum from protocol-base.\nexport const SignalType = {\n\tClientJoin: \"join\", // same value as MessageType.ClientJoin,\n\tClientLeave: \"leave\", // same value as MessageType.ClientLeave,\n\tClear: \"clear\", // used only by client for synthetic signals\n} as const;\n\ninterface SystemSignalContent {\n\ttype: (typeof SignalType)[keyof typeof SignalType];\n\tcontent?: unknown;\n}\n\ninterface InboundSystemSignal<TSignalContent extends SystemSignalContent>\n\textends ISignalMessage<{ type: never; content: TSignalContent }> {\n\t// eslint-disable-next-line @rushstack/no-new-null -- `null` is used in JSON protocol to indicate system message\n\treadonly clientId: null;\n}\n\ntype ClientJoinSignal = InboundSystemSignal<{\n\ttype: typeof SignalType.ClientJoin;\n\tcontent: ISignalClient;\n}>;\n\ntype ClientLeaveSignal = InboundSystemSignal<{\n\ttype: typeof SignalType.ClientLeave;\n\tcontent: string; // clientId of leaving client\n}>;\n\ntype ClearClientsSignal = InboundSystemSignal<{\n\ttype: typeof SignalType.Clear;\n}>;\n\ntype AudienceSignal = ClientJoinSignal | ClientLeaveSignal | ClearClientsSignal;\n\n/**\n * Function to be used for creating a protocol handler.\n * @legacy @beta\n */\nexport type ProtocolHandlerBuilder = (\n\tattributes: IDocumentAttributes,\n\tsnapshot: IQuorumSnapshot,\n\t// TODO: use a real type (breaking change)\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tsendProposal: (key: string, value: any) => number,\n) => IProtocolHandler;\n\n/**\n * @legacy @beta\n */\nexport interface IProtocolHandler extends IBaseProtocolHandler {\n\treadonly audience: IAudienceOwner;\n\tprocessSignal(message: ISignalMessage);\n}\n\n/**\n * More specific version of {@link IProtocolHandler} with narrower call\n * constraints for {@link IProtocolHandler.processSignal}.\n */\nexport interface ProtocolHandlerInternal extends IProtocolHandler {\n\t/**\n\t * Process the audience related signal.\n\t * @privateRemarks\n\t * Internally, only {@link AudienceSignal} messages need handling.\n\t */\n\tprocessSignal(message: AudienceSignal): void;\n}\n\n/**\n * Function to be used for creating a protocol handler.\n *\n * @remarks This is the same are {@link ProtocolHandlerBuilder} but\n * returns the {@link ProtocolHandlerInternal} which has narrower\n * expectations for `processSignal`.\n */\nexport type InternalProtocolHandlerBuilder = (\n\tattributes: IDocumentAttributes,\n\tsnapshot: IQuorumSnapshot,\n\t// TODO: use a real type (breaking change)\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tsendProposal: (key: string, value: any) => number,\n) => ProtocolHandlerInternal;\n\nexport class ProtocolHandler extends ProtocolOpHandler implements ProtocolHandlerInternal {\n\tconstructor(\n\t\tattributes: IDocumentAttributes,\n\t\tquorumSnapshot: IQuorumSnapshot,\n\t\t// TODO: use a real type (breaking change)\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tsendProposal: (key: string, value: any) => number,\n\t\tpublic readonly audience: IAudienceOwner,\n\t\tprivate readonly shouldClientHaveLeft: (clientId: string) => boolean,\n\t) {\n\t\tsuper(\n\t\t\tattributes.minimumSequenceNumber,\n\t\t\tattributes.sequenceNumber,\n\t\t\tquorumSnapshot.members,\n\t\t\tquorumSnapshot.proposals,\n\t\t\tquorumSnapshot.values,\n\t\t\tsendProposal,\n\t\t);\n\n\t\tfor (const [clientId, member] of this.quorum.getMembers()) {\n\t\t\taudience.addMember(clientId, member.client);\n\t\t}\n\n\t\t// Join / leave signals are ignored for \"write\" clients in favor of join / leave ops\n\t\tthis.quorum.on(\"addMember\", (clientId, details) =>\n\t\t\taudience.addMember(clientId, details.client),\n\t\t);\n\t\tthis.quorum.on(\"removeMember\", (clientId) => audience.removeMember(clientId));\n\t}\n\n\tpublic processMessage(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tlocal: boolean,\n\t): IProcessMessageResult {\n\t\t// Check and report if we're getting messages from a clientId that we previously\n\t\t// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be\n\t\t// eslint-disable-next-line unicorn/no-null\n\t\tif (message.clientId != null) {\n\t\t\tconst client = this.quorum.getMember(message.clientId);\n\n\t\t\tif (client === undefined && message.type !== MessageType.ClientJoin) {\n\t\t\t\t// pre-0.58 error message: messageClientIdMissingFromQuorum\n\t\t\t\tthrow new Error(\"Remote message's clientId is missing from the quorum\");\n\t\t\t}\n\n\t\t\t// Here checking canBeCoalescedByService is used as an approximation of \"is benign to process despite being unexpected\".\n\t\t\t// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the\n\t\t\t// document we don't need to blow up aggressively.\n\t\t\tif (this.shouldClientHaveLeft(message.clientId) && !canBeCoalescedByService(message)) {\n\t\t\t\t// pre-0.58 error message: messageClientIdShouldHaveLeft\n\t\t\t\tthrow new Error(\"Remote message's clientId already should have left\");\n\t\t\t}\n\t\t}\n\n\t\treturn super.processMessage(message, local);\n\t}\n\n\tpublic processSignal(message: AudienceSignal): void {\n\t\tconst innerContent = message.content;\n\t\tswitch (innerContent.type) {\n\t\t\tcase SignalType.Clear: {\n\t\t\t\tconst members = this.audience.getMembers();\n\t\t\t\tfor (const [clientId, client] of members) {\n\t\t\t\t\tif (client.mode === \"read\") {\n\t\t\t\t\t\tthis.audience.removeMember(clientId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientJoin: {\n\t\t\t\tconst newClient = innerContent.content;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (newClient.client.mode === \"read\") {\n\t\t\t\t\tthis.audience.addMember(newClient.clientId, newClient.client);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientLeave: {\n\t\t\t\tconst leftClientId = innerContent.content;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (this.audience.getMember(leftClientId)?.mode === \"read\") {\n\t\t\t\t\tthis.audience.removeMember(leftClientId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Function to check whether the protocol handler should process the Signal.\n * The protocol handler should strictly handle only ClientJoin, ClientLeave\n * and Clear signal types.\n */\nexport function protocolHandlerShouldProcessSignal(\n\tmessage: ISignalMessage,\n): message is AudienceSignal {\n\t// Signal originates from server\n\tif (message.clientId === null) {\n\t\tconst innerContent = message.content as { content: unknown; type: string };\n\t\treturn (\n\t\t\tinnerContent.type === SignalType.Clear ||\n\t\t\tinnerContent.type === SignalType.ClientJoin ||\n\t\t\tinnerContent.type === SignalType.ClientLeave\n\t\t);\n\t}\n\treturn false;\n}\n\nexport function wrapProtocolHandlerBuilder(\n\tbuilder: ProtocolHandlerBuilder,\n\tsignalAudience: IAudienceOwner,\n): InternalProtocolHandlerBuilder {\n\treturn (\n\t\tattributes: IDocumentAttributes,\n\t\tsnapshot: IQuorumSnapshot,\n\t\tsendProposal: (key: string, value: unknown) => number,\n\t): ProtocolHandlerInternal => {\n\t\tconst baseHandler = builder(attributes, snapshot, sendProposal);\n\t\t// Create proxy handler with an overridden processSignal method.\n\t\t// Use a Proxy since base may use [dynamic] property getters.\n\t\treturn new Proxy(baseHandler, {\n\t\t\tget(target, prop, receiver) {\n\t\t\t\tif (prop === \"processSignal\") {\n\t\t\t\t\treturn (message: AudienceSignal) => {\n\t\t\t\t\t\tconst innerContent = message.content;\n\t\t\t\t\t\tswitch (innerContent.type) {\n\t\t\t\t\t\t\tcase SignalType.Clear: {\n\t\t\t\t\t\t\t\tconst members = signalAudience.getMembers();\n\t\t\t\t\t\t\t\tfor (const clientId of members.keys()) {\n\t\t\t\t\t\t\t\t\tsignalAudience.removeMember(clientId);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase SignalType.ClientJoin: {\n\t\t\t\t\t\t\t\tconst newClient = innerContent.content;\n\t\t\t\t\t\t\t\tsignalAudience.addMember(newClient.clientId, newClient.client);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase SignalType.ClientLeave: {\n\t\t\t\t\t\t\t\tconst leftClientId = innerContent.content;\n\t\t\t\t\t\t\t\tsignalAudience.removeMember(leftClientId);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdefault: {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttarget.processSignal(message);\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\t\t\treturn Reflect.get(target, prop, receiver);\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
@@ -7,6 +7,9 @@
7
7
  * the protocol summary, using the provided callback to add it if necessary.
8
8
  */
9
9
  export class ProtocolTreeStorageService {
10
+ internalStorageService;
11
+ addProtocolSummaryIfMissing;
12
+ shouldSummarizeProtocolTree;
10
13
  /**
11
14
  *
12
15
  * @param internalStorageService - Document storage service responsible to make api calls to the storage.
@@ -31,6 +34,13 @@ export class ProtocolTreeStorageService {
31
34
  get disposed() {
32
35
  return this.internalStorageService.disposed;
33
36
  }
37
+ getSnapshotTree;
38
+ getSnapshot;
39
+ getVersions;
40
+ createBlob;
41
+ readBlob;
42
+ downloadSummary;
43
+ dispose;
34
44
  async uploadSummaryWithContext(summary, context) {
35
45
  return this.shouldSummarizeProtocolTree()
36
46
  ? this.internalStorageService.uploadSummaryWithContext(this.addProtocolSummaryIfMissing(summary), context)