@fluid-experimental/tree 2.118.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 (164) hide show
  1. package/CHANGELOG.md +22 -4
  2. package/README.md +52 -48
  3. package/{api-extractor.json → api-extractor/api-extractor-model.json} +1 -6
  4. package/api-extractor/api-extractor-report.json +9 -0
  5. package/dist/ChangeCompression.js +4 -3
  6. package/dist/ChangeCompression.js.map +1 -1
  7. package/dist/ChangeTypes.d.ts.map +1 -1
  8. package/dist/Checkout.js +28 -1
  9. package/dist/Checkout.js.map +1 -1
  10. package/dist/Common.js +27 -27
  11. package/dist/Common.js.map +1 -1
  12. package/dist/EditLog.d.ts.map +1 -1
  13. package/dist/EditLog.js +15 -11
  14. package/dist/EditLog.js.map +1 -1
  15. package/dist/EditUtilities.js +18 -18
  16. package/dist/EditUtilities.js.map +1 -1
  17. package/dist/Forest.js +12 -3
  18. package/dist/Forest.js.map +1 -1
  19. package/dist/HistoryEditFactory.js +2 -2
  20. package/dist/HistoryEditFactory.js.map +1 -1
  21. package/dist/IdConversion.js +4 -5
  22. package/dist/IdConversion.js.map +1 -1
  23. package/dist/Identifiers.js +1 -2
  24. package/dist/Identifiers.js.map +1 -1
  25. package/dist/LazyCheckout.js +1 -0
  26. package/dist/LazyCheckout.js.map +1 -1
  27. package/dist/LogViewer.js +50 -16
  28. package/dist/LogViewer.js.map +1 -1
  29. package/dist/MergeHealth.js +121 -123
  30. package/dist/MergeHealth.js.map +1 -1
  31. package/dist/NodeIdUtilities.js +4 -5
  32. package/dist/NodeIdUtilities.js.map +1 -1
  33. package/dist/PayloadUtilities.js +1 -2
  34. package/dist/PayloadUtilities.js.map +1 -1
  35. package/dist/RevisionValueCache.js +18 -6
  36. package/dist/RevisionValueCache.js.map +1 -1
  37. package/dist/RevisionView.js +3 -3
  38. package/dist/RevisionView.js.map +1 -1
  39. package/dist/SerializationUtilities.js +4 -5
  40. package/dist/SerializationUtilities.js.map +1 -1
  41. package/dist/SharedTree.js +69 -33
  42. package/dist/SharedTree.js.map +1 -1
  43. package/dist/SharedTreeEncoder.js +4 -2
  44. package/dist/SharedTreeEncoder.js.map +1 -1
  45. package/dist/StringInterner.js +2 -2
  46. package/dist/StringInterner.js.map +1 -1
  47. package/dist/Summary.js +1 -2
  48. package/dist/Summary.js.map +1 -1
  49. package/dist/SummaryBackCompatibility.js +2 -3
  50. package/dist/SummaryBackCompatibility.js.map +1 -1
  51. package/dist/SummaryTestUtilities.js +2 -3
  52. package/dist/SummaryTestUtilities.js.map +1 -1
  53. package/dist/Transaction.js +4 -0
  54. package/dist/Transaction.js.map +1 -1
  55. package/dist/TransactionInternal.js +8 -7
  56. package/dist/TransactionInternal.js.map +1 -1
  57. package/dist/TreeCompressor.js +2 -0
  58. package/dist/TreeCompressor.js.map +1 -1
  59. package/dist/TreeNodeHandle.js +2 -0
  60. package/dist/TreeNodeHandle.js.map +1 -1
  61. package/dist/TreeView.js +9 -0
  62. package/dist/TreeView.js.map +1 -1
  63. package/dist/TreeViewUtilities.js +3 -4
  64. package/dist/TreeViewUtilities.js.map +1 -1
  65. package/dist/UndoRedoHandler.js +9 -6
  66. package/dist/UndoRedoHandler.js.map +1 -1
  67. package/dist/UuidUtilities.js +6 -6
  68. package/dist/UuidUtilities.js.map +1 -1
  69. package/dist/id-compressor/AppendOnlySortedMap.js +11 -8
  70. package/dist/id-compressor/AppendOnlySortedMap.js.map +1 -1
  71. package/dist/id-compressor/IdCompressor.js +73 -61
  72. package/dist/id-compressor/IdCompressor.js.map +1 -1
  73. package/dist/id-compressor/IdRange.js +1 -2
  74. package/dist/id-compressor/IdRange.js.map +1 -1
  75. package/dist/id-compressor/NumericUuid.js +7 -8
  76. package/dist/id-compressor/NumericUuid.js.map +1 -1
  77. package/dist/id-compressor/SessionIdNormalizer.js +9 -8
  78. package/dist/id-compressor/SessionIdNormalizer.js.map +1 -1
  79. package/dist/migration-shim/migrationDeltaHandler.js +6 -1
  80. package/dist/migration-shim/migrationDeltaHandler.js.map +1 -1
  81. package/dist/migration-shim/migrationShim.js +46 -30
  82. package/dist/migration-shim/migrationShim.js.map +1 -1
  83. package/dist/migration-shim/migrationShimFactory.js +3 -0
  84. package/dist/migration-shim/migrationShimFactory.js.map +1 -1
  85. package/dist/migration-shim/sharedTreeDeltaHandler.js +3 -1
  86. package/dist/migration-shim/sharedTreeDeltaHandler.js.map +1 -1
  87. package/dist/migration-shim/sharedTreeShim.js +7 -0
  88. package/dist/migration-shim/sharedTreeShim.js.map +1 -1
  89. package/dist/migration-shim/sharedTreeShimFactory.js +1 -0
  90. package/dist/migration-shim/sharedTreeShimFactory.js.map +1 -1
  91. package/dist/migration-shim/shimChannelServices.js +1 -0
  92. package/dist/migration-shim/shimChannelServices.js.map +1 -1
  93. package/dist/migration-shim/shimDeltaConnection.js +6 -1
  94. package/dist/migration-shim/shimDeltaConnection.js.map +1 -1
  95. package/dist/migration-shim/shimHandle.js +1 -0
  96. package/dist/migration-shim/shimHandle.js.map +1 -1
  97. package/dist/migration-shim/utils.js +3 -4
  98. package/dist/migration-shim/utils.js.map +1 -1
  99. package/dist/persisted-types/0.1.1.d.ts.map +1 -1
  100. package/lib/ChangeCompression.js +1 -0
  101. package/lib/ChangeCompression.js.map +1 -1
  102. package/lib/ChangeTypes.d.ts.map +1 -1
  103. package/lib/Checkout.js +28 -1
  104. package/lib/Checkout.js.map +1 -1
  105. package/lib/Common.js +1 -1
  106. package/lib/Common.js.map +1 -1
  107. package/lib/EditLog.d.ts.map +1 -1
  108. package/lib/EditLog.js +12 -8
  109. package/lib/EditLog.js.map +1 -1
  110. package/lib/Forest.js +9 -0
  111. package/lib/Forest.js.map +1 -1
  112. package/lib/LazyCheckout.js +1 -0
  113. package/lib/LazyCheckout.js.map +1 -1
  114. package/lib/LogViewer.js +50 -16
  115. package/lib/LogViewer.js.map +1 -1
  116. package/lib/MergeHealth.js +119 -121
  117. package/lib/MergeHealth.js.map +1 -1
  118. package/lib/RevisionValueCache.js +18 -6
  119. package/lib/RevisionValueCache.js.map +1 -1
  120. package/lib/SharedTree.js +69 -33
  121. package/lib/SharedTree.js.map +1 -1
  122. package/lib/SharedTreeEncoder.js +4 -2
  123. package/lib/SharedTreeEncoder.js.map +1 -1
  124. package/lib/StringInterner.js +2 -2
  125. package/lib/StringInterner.js.map +1 -1
  126. package/lib/Transaction.js +4 -0
  127. package/lib/Transaction.js.map +1 -1
  128. package/lib/TransactionInternal.js +8 -7
  129. package/lib/TransactionInternal.js.map +1 -1
  130. package/lib/TreeCompressor.js +2 -0
  131. package/lib/TreeCompressor.js.map +1 -1
  132. package/lib/TreeNodeHandle.js +2 -0
  133. package/lib/TreeNodeHandle.js.map +1 -1
  134. package/lib/TreeView.js +9 -0
  135. package/lib/TreeView.js.map +1 -1
  136. package/lib/UndoRedoHandler.js +9 -6
  137. package/lib/UndoRedoHandler.js.map +1 -1
  138. package/lib/id-compressor/AppendOnlySortedMap.js +11 -8
  139. package/lib/id-compressor/AppendOnlySortedMap.js.map +1 -1
  140. package/lib/id-compressor/IdCompressor.js +69 -57
  141. package/lib/id-compressor/IdCompressor.js.map +1 -1
  142. package/lib/id-compressor/SessionIdNormalizer.js +9 -8
  143. package/lib/id-compressor/SessionIdNormalizer.js.map +1 -1
  144. package/lib/migration-shim/migrationDeltaHandler.js +6 -1
  145. package/lib/migration-shim/migrationDeltaHandler.js.map +1 -1
  146. package/lib/migration-shim/migrationShim.js +46 -30
  147. package/lib/migration-shim/migrationShim.js.map +1 -1
  148. package/lib/migration-shim/migrationShimFactory.js +3 -0
  149. package/lib/migration-shim/migrationShimFactory.js.map +1 -1
  150. package/lib/migration-shim/sharedTreeDeltaHandler.js +3 -1
  151. package/lib/migration-shim/sharedTreeDeltaHandler.js.map +1 -1
  152. package/lib/migration-shim/sharedTreeShim.js +7 -0
  153. package/lib/migration-shim/sharedTreeShim.js.map +1 -1
  154. package/lib/migration-shim/sharedTreeShimFactory.js +1 -0
  155. package/lib/migration-shim/sharedTreeShimFactory.js.map +1 -1
  156. package/lib/migration-shim/shimChannelServices.js +1 -0
  157. package/lib/migration-shim/shimChannelServices.js.map +1 -1
  158. package/lib/migration-shim/shimDeltaConnection.js +6 -1
  159. package/lib/migration-shim/shimDeltaConnection.js.map +1 -1
  160. package/lib/migration-shim/shimHandle.js +1 -0
  161. package/lib/migration-shim/shimHandle.js.map +1 -1
  162. package/lib/persisted-types/0.1.1.d.ts.map +1 -1
  163. package/package.json +37 -41
  164. package/tsconfig.json +3 -4
@@ -4,7 +4,8 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.SharedTreeMergeHealthTelemetryHeartbeat = exports.useFailedSequencedEditTelemetry = void 0;
7
+ exports.SharedTreeMergeHealthTelemetryHeartbeat = void 0;
8
+ exports.useFailedSequencedEditTelemetry = useFailedSequencedEditTelemetry;
8
9
  const core_interfaces_1 = require("@fluidframework/core-interfaces");
9
10
  const Common_js_1 = require("./Common.js");
10
11
  const EditUtilities_js_1 = require("./EditUtilities.js");
@@ -33,133 +34,13 @@ function useFailedSequencedEditTelemetry(tree) {
33
34
  },
34
35
  };
35
36
  }
36
- exports.useFailedSequencedEditTelemetry = useFailedSequencedEditTelemetry;
37
37
  /**
38
38
  * Aggregates and logs telemetry about the success of concurrent edits.
39
39
  * @internal
40
40
  */
41
41
  class SharedTreeMergeHealthTelemetryHeartbeat {
42
- constructor() {
43
- this.heartbeatTimerId = 0;
44
- this.treeData = new Map();
45
- /**
46
- * Receives SequencedEditApplied events from trees.
47
- */
48
- this.sequencedEditHandler = (params) => {
49
- const { edit, tree, wasLocal, logger, outcome, reconciliationPath } = params;
50
- if (wasLocal) {
51
- const tallyAndLogger = this.treeData.get(tree) ?? (0, Common_js_1.fail)('Should only receive events for registered trees');
52
- tallyAndLogger.logger = logger;
53
- const tally = tallyAndLogger.tally;
54
- tally.editCount += 1;
55
- tally.pathLengths[reconciliationPath.length] = (tally.pathLengths[reconciliationPath.length] ?? 0) + 1;
56
- if (edit.pastAttemptCount !== undefined && edit.pastAttemptCount > tally.maxAttemptCount) {
57
- tally.maxAttemptCount = edit.pastAttemptCount;
58
- }
59
- if (outcome.status !== index_js_1.EditStatus.Applied) {
60
- tally.failedEditCount += 1;
61
- switch (outcome.failure.kind) {
62
- case TransactionInternal_js_1.TransactionInternal.FailureKind.BadPlace: {
63
- tally.badPlaceCount += 1;
64
- if (outcome.failure.placeFailure === EditUtilities_js_1.PlaceValidationResult.MissingSibling) {
65
- tally.deletedSiblingBadPlaceCount += 1;
66
- }
67
- else if (outcome.failure.placeFailure === EditUtilities_js_1.PlaceValidationResult.MissingParent) {
68
- tally.deletedAncestorBadPlaceCount += 1;
69
- }
70
- break;
71
- }
72
- case TransactionInternal_js_1.TransactionInternal.FailureKind.BadRange: {
73
- tally.badRangeCount += 1;
74
- switch (outcome.failure.rangeFailure) {
75
- case EditUtilities_js_1.RangeValidationResultKind.Inverted:
76
- tally.updatedRangeInvertedCount += 1;
77
- break;
78
- case EditUtilities_js_1.RangeValidationResultKind.PlacesInDifferentTraits:
79
- tally.updatedRangeHasPlacesInDifferentTraitsCount += 1;
80
- break;
81
- default:
82
- // 'rangeFailure' is either a RangeValidationResultKind (handled above), or an object
83
- // with a nested 'kind' property containing the RangeValidationResultKind (handled below).
84
- switch (outcome.failure.rangeFailure?.kind) {
85
- case EditUtilities_js_1.RangeValidationResultKind.BadPlace:
86
- tally.updatedRangeBadPlaceCount += 1;
87
- break;
88
- default:
89
- break;
90
- }
91
- }
92
- break;
93
- }
94
- case TransactionInternal_js_1.TransactionInternal.FailureKind.ConstraintViolation: {
95
- tally.constraintViolationCount += 1;
96
- switch (outcome.failure.violation.kind) {
97
- case TransactionInternal_js_1.TransactionInternal.ConstraintViolationKind.BadRange: {
98
- tally.rangeConstraintViolationCount += 1;
99
- }
100
- case TransactionInternal_js_1.TransactionInternal.ConstraintViolationKind.BadLength: {
101
- tally.lengthConstraintViolationCount += 1;
102
- break;
103
- }
104
- case TransactionInternal_js_1.TransactionInternal.ConstraintViolationKind.BadParent: {
105
- tally.parentConstraintViolationCount += 1;
106
- break;
107
- }
108
- case TransactionInternal_js_1.TransactionInternal.ConstraintViolationKind.BadLabel: {
109
- tally.labelConstraintViolationCount += 1;
110
- break;
111
- }
112
- default: {
113
- // If this doesn't type-check, the above switch statement needs to be extended to handle a new case.
114
- const _ = outcome.failure.violation;
115
- }
116
- }
117
- break;
118
- }
119
- case TransactionInternal_js_1.TransactionInternal.FailureKind.IdAlreadyInUse: {
120
- tally.idAlreadyInUseCount += 1;
121
- break;
122
- }
123
- case TransactionInternal_js_1.TransactionInternal.FailureKind.UnknownId: {
124
- tally.unknownIdCount += 1;
125
- break;
126
- }
127
- case TransactionInternal_js_1.TransactionInternal.FailureKind.DetachedSequenceIdAlreadyInUse:
128
- case TransactionInternal_js_1.TransactionInternal.FailureKind.DetachedSequenceNotFound:
129
- case TransactionInternal_js_1.TransactionInternal.FailureKind.DuplicateIdInBuild:
130
- case TransactionInternal_js_1.TransactionInternal.FailureKind.UnusedDetachedSequence: {
131
- tally.malformedEditCount += 1;
132
- break;
133
- }
134
- default: {
135
- // If this doesn't type-check, the above switch statement needs to be extended to handle a new case.
136
- const _ = outcome.failure;
137
- }
138
- }
139
- }
140
- }
141
- };
142
- /**
143
- * Logs the accumulated merge health data to each tree's designated logger.
144
- */
145
- this.logHeartbeat = () => {
146
- for (const [tree, { tally, logger }] of this.treeData) {
147
- if (logger && tally.editCount > 0) {
148
- // Note: all this data is for sequenced edits that were originally produced by the local client.
149
- logger.send({
150
- category: 'Heartbeat',
151
- eventName: 'EditMergeHealth',
152
- ...tally,
153
- // The counts of occurrences for a given path length.
154
- // '1:2' means two occurrences of length one.
155
- // Overwrites `tally.pathLengths` which is incompatible with ITelemetryBaseEvent.
156
- pathLengths: pathLengthsCounts(tally.pathLengths),
157
- }, core_interfaces_1.LogLevel.essential);
158
- this.resetTreeData(tree);
159
- }
160
- }
161
- };
162
- }
42
+ heartbeatTimerId = 0;
43
+ treeData = new Map();
163
44
  /**
164
45
  * Adds a tree to the set of tree to log merge health telemetry for.
165
46
  * Noop if such a tree was already in the set.
@@ -264,6 +145,123 @@ class SharedTreeMergeHealthTelemetryHeartbeat {
264
145
  this.resetTreeData(tree);
265
146
  }
266
147
  }
148
+ /**
149
+ * Receives SequencedEditApplied events from trees.
150
+ */
151
+ sequencedEditHandler = (params) => {
152
+ const { edit, tree, wasLocal, logger, outcome, reconciliationPath } = params;
153
+ if (wasLocal) {
154
+ const tallyAndLogger = this.treeData.get(tree) ?? (0, Common_js_1.fail)('Should only receive events for registered trees');
155
+ tallyAndLogger.logger = logger;
156
+ const tally = tallyAndLogger.tally;
157
+ tally.editCount += 1;
158
+ tally.pathLengths[reconciliationPath.length] = (tally.pathLengths[reconciliationPath.length] ?? 0) + 1;
159
+ if (edit.pastAttemptCount !== undefined && edit.pastAttemptCount > tally.maxAttemptCount) {
160
+ tally.maxAttemptCount = edit.pastAttemptCount;
161
+ }
162
+ if (outcome.status !== index_js_1.EditStatus.Applied) {
163
+ tally.failedEditCount += 1;
164
+ switch (outcome.failure.kind) {
165
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.BadPlace: {
166
+ tally.badPlaceCount += 1;
167
+ if (outcome.failure.placeFailure === EditUtilities_js_1.PlaceValidationResult.MissingSibling) {
168
+ tally.deletedSiblingBadPlaceCount += 1;
169
+ }
170
+ else if (outcome.failure.placeFailure === EditUtilities_js_1.PlaceValidationResult.MissingParent) {
171
+ tally.deletedAncestorBadPlaceCount += 1;
172
+ }
173
+ break;
174
+ }
175
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.BadRange: {
176
+ tally.badRangeCount += 1;
177
+ switch (outcome.failure.rangeFailure) {
178
+ case EditUtilities_js_1.RangeValidationResultKind.Inverted:
179
+ tally.updatedRangeInvertedCount += 1;
180
+ break;
181
+ case EditUtilities_js_1.RangeValidationResultKind.PlacesInDifferentTraits:
182
+ tally.updatedRangeHasPlacesInDifferentTraitsCount += 1;
183
+ break;
184
+ default:
185
+ // 'rangeFailure' is either a RangeValidationResultKind (handled above), or an object
186
+ // with a nested 'kind' property containing the RangeValidationResultKind (handled below).
187
+ switch (outcome.failure.rangeFailure?.kind) {
188
+ case EditUtilities_js_1.RangeValidationResultKind.BadPlace:
189
+ tally.updatedRangeBadPlaceCount += 1;
190
+ break;
191
+ default:
192
+ break;
193
+ }
194
+ }
195
+ break;
196
+ }
197
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.ConstraintViolation: {
198
+ tally.constraintViolationCount += 1;
199
+ switch (outcome.failure.violation.kind) {
200
+ case TransactionInternal_js_1.TransactionInternal.ConstraintViolationKind.BadRange: {
201
+ tally.rangeConstraintViolationCount += 1;
202
+ }
203
+ case TransactionInternal_js_1.TransactionInternal.ConstraintViolationKind.BadLength: {
204
+ tally.lengthConstraintViolationCount += 1;
205
+ break;
206
+ }
207
+ case TransactionInternal_js_1.TransactionInternal.ConstraintViolationKind.BadParent: {
208
+ tally.parentConstraintViolationCount += 1;
209
+ break;
210
+ }
211
+ case TransactionInternal_js_1.TransactionInternal.ConstraintViolationKind.BadLabel: {
212
+ tally.labelConstraintViolationCount += 1;
213
+ break;
214
+ }
215
+ default: {
216
+ // If this doesn't type-check, the above switch statement needs to be extended to handle a new case.
217
+ const _ = outcome.failure.violation;
218
+ }
219
+ }
220
+ break;
221
+ }
222
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.IdAlreadyInUse: {
223
+ tally.idAlreadyInUseCount += 1;
224
+ break;
225
+ }
226
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.UnknownId: {
227
+ tally.unknownIdCount += 1;
228
+ break;
229
+ }
230
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.DetachedSequenceIdAlreadyInUse:
231
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.DetachedSequenceNotFound:
232
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.DuplicateIdInBuild:
233
+ case TransactionInternal_js_1.TransactionInternal.FailureKind.UnusedDetachedSequence: {
234
+ tally.malformedEditCount += 1;
235
+ break;
236
+ }
237
+ default: {
238
+ // If this doesn't type-check, the above switch statement needs to be extended to handle a new case.
239
+ const _ = outcome.failure;
240
+ }
241
+ }
242
+ }
243
+ }
244
+ };
245
+ /**
246
+ * Logs the accumulated merge health data to each tree's designated logger.
247
+ */
248
+ logHeartbeat = () => {
249
+ for (const [tree, { tally, logger }] of this.treeData) {
250
+ if (logger && tally.editCount > 0) {
251
+ // Note: all this data is for sequenced edits that were originally produced by the local client.
252
+ logger.send({
253
+ category: 'Heartbeat',
254
+ eventName: 'EditMergeHealth',
255
+ ...tally,
256
+ // The counts of occurrences for a given path length.
257
+ // '1:2' means two occurrences of length one.
258
+ // Overwrites `tally.pathLengths` which is incompatible with ITelemetryBaseEvent.
259
+ pathLengths: pathLengthsCounts(tally.pathLengths),
260
+ }, core_interfaces_1.LogLevel.essential);
261
+ this.resetTreeData(tree);
262
+ }
263
+ }
264
+ };
267
265
  }
268
266
  exports.SharedTreeMergeHealthTelemetryHeartbeat = SharedTreeMergeHealthTelemetryHeartbeat;
269
267
  function pathLengthsCounts(lengths) {
@@ -1 +1 @@
1
- {"version":3,"file":"MergeHealth.js","sourceRoot":"","sources":["../src/MergeHealth.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 { LogLevel } from '@fluidframework/core-interfaces';\nimport type { ITelemetryBaseLogger } from '@fluidframework/core-interfaces';\n\nimport { assertNotUndefined, fail } from './Common.js';\nimport { PlaceValidationResult, RangeValidationResultKind } from './EditUtilities.js';\nimport { SharedTreeEvent } from './EventTypes.js';\nimport { SequencedEditAppliedEventArguments, SharedTree } from './SharedTree.js';\nimport { TransactionInternal } from './TransactionInternal.js';\nimport { EditStatus } from './persisted-types/index.js';\n\n/**\n * Logs generic telemetry for failed sequenced edits.\n * Only failing edits that were originally made locally are logged.\n * @param tree - The tree for which to log the telemetry.\n * @internal\n */\nexport function useFailedSequencedEditTelemetry(tree: SharedTree): { disable: () => void } {\n\tfunction onEdit({ wasLocal, logger, outcome }: SequencedEditAppliedEventArguments): void {\n\t\tif (wasLocal && outcome.status !== EditStatus.Applied) {\n\t\t\tlogger.send(\n\t\t\t\t{\n\t\t\t\t\tcategory: 'generic',\n\t\t\t\t\teventName: outcome.status === EditStatus.Malformed ? 'MalformedSharedTreeEdit' : 'InvalidSharedTreeEdit',\n\t\t\t\t},\n\t\t\t\tLogLevel.essential\n\t\t\t);\n\t\t}\n\t}\n\ttree.on(SharedTreeEvent.SequencedEditApplied, onEdit);\n\treturn {\n\t\tdisable: () => {\n\t\t\ttree.off(SharedTreeEvent.SequencedEditApplied, onEdit);\n\t\t},\n\t};\n}\n\n/**\n * Statistics about the health of collaborative edit merging when using {@link SharedTree}.\n * All of those numbers constitute a tally since the last heartbeat was logged or cleared.\n * @internal\n */\nexport interface MergeHealthStats {\n\t/** Number of sequenced edits applied (failed or not). */\n\teditCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply.\n\t * Such cases are also counted under {@link MergeHealthStats.editCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.badPlaceCount}\n\t * * {@link MergeHealthStats.badRangeCount}\n\t * * {@link MergeHealthStats.constraintViolationCount}\n\t * * {@link MergeHealthStats.idAlreadyInUseCount}\n\t * * {@link MergeHealthStats.unknownIdCount}\n\t * * {@link MergeHealthStats.malformedEditCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tfailedEditCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a bad place.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.deletedAncestorBadPlaceCount}\n\t * * {@link MergeHealthStats.deletedSiblingBadPlaceCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tbadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a bad range.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.deletedAncestorBadRangeCount}\n\t * * {@link MergeHealthStats.deletedSiblingBadRangeCount}\n\t * * {@link MergeHealthStats.updatedRangeInvertedCount}\n\t * * {@link MergeHealthStats.updatedRangeHasPlacesInDifferentTraitsCount}\n\t * * {@link MergeHealthStats.updatedRangeBadPlaceCount}\n\t * * {@link MergeHealthStats.updatedRangeNeverValidPlaceCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tbadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a place whose ancestors had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badPlaceCount}.\n\t */\n\tdeletedAncestorBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose ancestors had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tdeletedAncestorBadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a place whose sibling (but not its parent) had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badPlaceCount}.\n\t */\n\tdeletedSiblingBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose delimiting sibling(s) (but not its parent) had been concurrently\n\t * deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tdeletedSiblingBadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but inverted (i.e., end before start).\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeInvertedCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but in different traits.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeHasPlacesInDifferentTraitsCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but bad.\n\t * This should not happen because place resolution is expected to either return a valid place or fail.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were not resolvable due to not being valid for any state.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeNeverValidPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constraint violation.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.rangeConstraintViolationCount}\n\t * * {@link MergeHealthStats.lengthConstraintViolationCount}\n\t * * {@link MergeHealthStats.parentConstraintViolationCount}\n\t * * {@link MergeHealthStats.labelConstraintViolationCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tconstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range becoming invalid or malformed.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\trangeConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range having a different length.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tlengthConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range being under a different parent.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tparentConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range being under a different label.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tlabelConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an ID collision.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tidAlreadyInUseCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an ID being unknown.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tunknownIdCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an edit becoming malformed.\n\t * This should theoretically never happen.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tmalformedEditCount: number;\n\n\t/**\n\t * The counts of occurrences for a given path length. `pathLengths[1] === 2` means two occurrences of length one.\n\t */\n\tpathLengths: number[];\n\n\t/** The highest number previous attempts on a sequenced edit. */\n\tmaxAttemptCount: number;\n}\n\n/**\n * Aggregates and logs telemetry about the success of concurrent edits.\n * @internal\n */\nexport class SharedTreeMergeHealthTelemetryHeartbeat {\n\tprivate heartbeatTimerId = 0;\n\tprivate readonly treeData = new Map<SharedTree, { tally: MergeHealthStats; logger?: ITelemetryBaseLogger }>();\n\n\t/**\n\t * Adds a tree to the set of tree to log merge health telemetry for.\n\t * Noop if such a tree was already in the set.\n\t * @param tree - The tree to log merge health telemetry for.\n\t */\n\tpublic attachTree(tree: SharedTree): void {\n\t\tif (this.treeData.has(tree) === false) {\n\t\t\tthis.resetTreeData(tree);\n\t\t\ttree.on(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);\n\t\t}\n\t}\n\n\t/**\n\t * Removes a tree from the set of tree to log merge health telemetry for.\n\t * Noop if such a tree was never in the set.\n\t * @param tree - The tree to stop logging merge health telemetry for.\n\t */\n\tpublic detachTree(tree: SharedTree): void {\n\t\tif (this.treeData.has(tree)) {\n\t\t\ttree.off(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);\n\t\t\tthis.treeData.delete(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Exposes the aggregated statistics about merge health for the given tree.\n\t * @param tree - The tree to get stats for.\n\t * @returns Aggregated statistics about merge health for the given tree.\n\t */\n\tpublic getStats(tree: SharedTree): MergeHealthStats {\n\t\treturn assertNotUndefined(this.treeData.get(tree), 'No such tree was attached to the logger').tally;\n\t}\n\n\t/**\n\t * Removes all trees from the set of tree to log merge health telemetry for.\n\t */\n\tpublic detachAllTrees(): void {\n\t\tfor (const tree of this.treeData.keys()) {\n\t\t\tthis.detachTree(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Resets the aggregated merge health data for the given tree.\n\t * @param tree - The tree to reset the merge health data for.\n\t */\n\tpublic resetTreeData(tree: SharedTree): void {\n\t\tthis.treeData.set(tree, {\n\t\t\ttally: {\n\t\t\t\tmaxAttemptCount: 0,\n\t\t\t\tpathLengths: [],\n\t\t\t\teditCount: 0,\n\t\t\t\tfailedEditCount: 0,\n\t\t\t\tbadPlaceCount: 0,\n\t\t\t\tbadRangeCount: 0,\n\n\t\t\t\tdeletedAncestorBadPlaceCount: 0,\n\t\t\t\tdeletedAncestorBadRangeCount: 0,\n\n\t\t\t\tdeletedSiblingBadPlaceCount: 0,\n\t\t\t\tdeletedSiblingBadRangeCount: 0,\n\t\t\t\tupdatedRangeInvertedCount: 0,\n\t\t\t\tupdatedRangeHasPlacesInDifferentTraitsCount: 0,\n\t\t\t\tupdatedRangeBadPlaceCount: 0,\n\t\t\t\tupdatedRangeNeverValidPlaceCount: 0,\n\n\t\t\t\tconstraintViolationCount: 0,\n\t\t\t\trangeConstraintViolationCount: 0,\n\t\t\t\tlengthConstraintViolationCount: 0,\n\t\t\t\tparentConstraintViolationCount: 0,\n\t\t\t\tlabelConstraintViolationCount: 0,\n\n\t\t\t\tidAlreadyInUseCount: 0,\n\t\t\t\tunknownIdCount: 0,\n\t\t\t\tmalformedEditCount: 0,\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Enables the regular telemetry logging of merge health data.\n\t * The first message will be sent after `interval` milliseconds. See {@link SharedTreeMergeHealthTelemetryHeartbeat.flushHeartbeat} for\n\t * immediate logging.\n\t * @param interval - The amount of time in milliseconds between log messages.\n\t */\n\tpublic startHeartbeat(interval: number = 60000): void {\n\t\tif (this.heartbeatTimerId !== 0) {\n\t\t\tthis.stopHeartbeat();\n\t\t}\n\t\tthis.heartbeatTimerId = window.setInterval(this.logHeartbeat, interval);\n\t}\n\n\t/**\n\t * Disables the regular telemetry logging of merge health data.\n\t */\n\tpublic stopHeartbeat(): void {\n\t\twindow.clearInterval(this.heartbeatTimerId);\n\t\tthis.heartbeatTimerId = 0;\n\t}\n\n\t/**\n\t * Sends all collected merge health data and resets the aggregated state.\n\t */\n\tpublic flushHeartbeat(): void {\n\t\tthis.logHeartbeat();\n\t\tthis.clearData();\n\t}\n\n\t/**\n\t * Resets all aggregated state.\n\t */\n\tpublic clearData(): void {\n\t\tfor (const tree of this.treeData.keys()) {\n\t\t\tthis.resetTreeData(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Receives SequencedEditApplied events from trees.\n\t */\n\tprivate readonly sequencedEditHandler = (params: SequencedEditAppliedEventArguments): void => {\n\t\tconst { edit, tree, wasLocal, logger, outcome, reconciliationPath } = params;\n\t\tif (wasLocal) {\n\t\t\tconst tallyAndLogger = this.treeData.get(tree) ?? fail('Should only receive events for registered trees');\n\t\t\ttallyAndLogger.logger = logger;\n\t\t\tconst tally = tallyAndLogger.tally;\n\t\t\ttally.editCount += 1;\n\t\t\ttally.pathLengths[reconciliationPath.length] = (tally.pathLengths[reconciliationPath.length] ?? 0) + 1;\n\t\t\tif (edit.pastAttemptCount !== undefined && edit.pastAttemptCount > tally.maxAttemptCount) {\n\t\t\t\ttally.maxAttemptCount = edit.pastAttemptCount;\n\t\t\t}\n\t\t\tif (outcome.status !== EditStatus.Applied) {\n\t\t\t\ttally.failedEditCount += 1;\n\t\t\t\tswitch (outcome.failure.kind) {\n\t\t\t\t\tcase TransactionInternal.FailureKind.BadPlace: {\n\t\t\t\t\t\ttally.badPlaceCount += 1;\n\t\t\t\t\t\tif (outcome.failure.placeFailure === PlaceValidationResult.MissingSibling) {\n\t\t\t\t\t\t\ttally.deletedSiblingBadPlaceCount += 1;\n\t\t\t\t\t\t} else if (outcome.failure.placeFailure === PlaceValidationResult.MissingParent) {\n\t\t\t\t\t\t\ttally.deletedAncestorBadPlaceCount += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.BadRange: {\n\t\t\t\t\t\ttally.badRangeCount += 1;\n\t\t\t\t\t\tswitch (outcome.failure.rangeFailure) {\n\t\t\t\t\t\t\tcase RangeValidationResultKind.Inverted:\n\t\t\t\t\t\t\t\ttally.updatedRangeInvertedCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase RangeValidationResultKind.PlacesInDifferentTraits:\n\t\t\t\t\t\t\t\ttally.updatedRangeHasPlacesInDifferentTraitsCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t// 'rangeFailure' is either a RangeValidationResultKind (handled above), or an object\n\t\t\t\t\t\t\t\t// with a nested 'kind' property containing the RangeValidationResultKind (handled below).\n\t\t\t\t\t\t\t\tswitch (outcome.failure.rangeFailure?.kind) {\n\t\t\t\t\t\t\t\t\tcase RangeValidationResultKind.BadPlace:\n\t\t\t\t\t\t\t\t\t\ttally.updatedRangeBadPlaceCount += 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.ConstraintViolation: {\n\t\t\t\t\t\ttally.constraintViolationCount += 1;\n\t\t\t\t\t\tswitch (outcome.failure.violation.kind) {\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadRange: {\n\t\t\t\t\t\t\t\ttally.rangeConstraintViolationCount += 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadLength: {\n\t\t\t\t\t\t\t\ttally.lengthConstraintViolationCount += 1;\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 TransactionInternal.ConstraintViolationKind.BadParent: {\n\t\t\t\t\t\t\t\ttally.parentConstraintViolationCount += 1;\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 TransactionInternal.ConstraintViolationKind.BadLabel: {\n\t\t\t\t\t\t\t\ttally.labelConstraintViolationCount += 1;\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\t// If this doesn't type-check, the above switch statement needs to be extended to handle a new case.\n\t\t\t\t\t\t\t\tconst _: never = outcome.failure.violation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.IdAlreadyInUse: {\n\t\t\t\t\t\ttally.idAlreadyInUseCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.UnknownId: {\n\t\t\t\t\t\ttally.unknownIdCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.DetachedSequenceIdAlreadyInUse:\n\t\t\t\t\tcase TransactionInternal.FailureKind.DetachedSequenceNotFound:\n\t\t\t\t\tcase TransactionInternal.FailureKind.DuplicateIdInBuild:\n\t\t\t\t\tcase TransactionInternal.FailureKind.UnusedDetachedSequence: {\n\t\t\t\t\t\ttally.malformedEditCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\t// If this doesn't type-check, the above switch statement needs to be extended to handle a new case.\n\t\t\t\t\t\tconst _: never = outcome.failure;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Logs the accumulated merge health data to each tree's designated logger.\n\t */\n\tprivate readonly logHeartbeat = (): void => {\n\t\tfor (const [tree, { tally, logger }] of this.treeData) {\n\t\t\tif (logger && tally.editCount > 0) {\n\t\t\t\t// Note: all this data is for sequenced edits that were originally produced by the local client.\n\t\t\t\tlogger.send(\n\t\t\t\t\t{\n\t\t\t\t\t\tcategory: 'Heartbeat',\n\t\t\t\t\t\teventName: 'EditMergeHealth',\n\t\t\t\t\t\t...tally,\n\t\t\t\t\t\t// The counts of occurrences for a given path length.\n\t\t\t\t\t\t// '1:2' means two occurrences of length one.\n\t\t\t\t\t\t// Overwrites `tally.pathLengths` which is incompatible with ITelemetryBaseEvent.\n\t\t\t\t\t\tpathLengths: pathLengthsCounts(tally.pathLengths),\n\t\t\t\t\t},\n\t\t\t\t\tLogLevel.essential\n\t\t\t\t);\n\t\t\t\tthis.resetTreeData(tree);\n\t\t\t}\n\t\t}\n\t};\n}\n\nfunction pathLengthsCounts(lengths: readonly number[]): string {\n\treturn Object.entries(lengths)\n\t\t.map(([length, count]) => `${length}:${count}`)\n\t\t.join(',');\n}\n"]}
1
+ {"version":3,"file":"MergeHealth.js","sourceRoot":"","sources":["../src/MergeHealth.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAkBH,0EAkBC;AAlCD,qEAA2D;AAG3D,2CAAuD;AACvD,yDAAsF;AACtF,mDAAkD;AAElD,qEAA+D;AAC/D,yDAAwD;AAExD;;;;;GAKG;AACH,SAAgB,+BAA+B,CAAC,IAAgB;IAC/D,SAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAsC;QAChF,IAAI,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,qBAAU,CAAC,OAAO,EAAE,CAAC;YACvD,MAAM,CAAC,IAAI,CACV;gBACC,QAAQ,EAAE,SAAS;gBACnB,SAAS,EAAE,OAAO,CAAC,MAAM,KAAK,qBAAU,CAAC,SAAS,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,uBAAuB;aACxG,EACD,0BAAQ,CAAC,SAAS,CAClB,CAAC;QACH,CAAC;IACF,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,+BAAe,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;IACtD,OAAO;QACN,OAAO,EAAE,GAAG,EAAE;YACb,IAAI,CAAC,GAAG,CAAC,+BAAe,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;QACxD,CAAC;KACD,CAAC;AACH,CAAC;AA2KD;;;GAGG;AACH,MAAa,uCAAuC;IAC3C,gBAAgB,GAAG,CAAC,CAAC;IACZ,QAAQ,GAAG,IAAI,GAAG,EAA0E,CAAC;IAE9G;;;;OAIG;IACI,UAAU,CAAC,IAAgB;QACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC;YACvC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YACzB,IAAI,CAAC,EAAE,CAAC,+BAAe,CAAC,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC1E,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,UAAU,CAAC,IAAgB;QACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,+BAAe,CAAC,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAC1E,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,QAAQ,CAAC,IAAgB;QAC/B,OAAO,IAAA,8BAAkB,EAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,yCAAyC,CAAC,CAAC,KAAK,CAAC;IACrG,CAAC;IAED;;OAEG;IACI,cAAc;QACpB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;YACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,aAAa,CAAC,IAAgB;QACpC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE;YACvB,KAAK,EAAE;gBACN,eAAe,EAAE,CAAC;gBAClB,WAAW,EAAE,EAAE;gBACf,SAAS,EAAE,CAAC;gBACZ,eAAe,EAAE,CAAC;gBAClB,aAAa,EAAE,CAAC;gBAChB,aAAa,EAAE,CAAC;gBAEhB,4BAA4B,EAAE,CAAC;gBAC/B,4BAA4B,EAAE,CAAC;gBAE/B,2BAA2B,EAAE,CAAC;gBAC9B,2BAA2B,EAAE,CAAC;gBAC9B,yBAAyB,EAAE,CAAC;gBAC5B,2CAA2C,EAAE,CAAC;gBAC9C,yBAAyB,EAAE,CAAC;gBAC5B,gCAAgC,EAAE,CAAC;gBAEnC,wBAAwB,EAAE,CAAC;gBAC3B,6BAA6B,EAAE,CAAC;gBAChC,8BAA8B,EAAE,CAAC;gBACjC,8BAA8B,EAAE,CAAC;gBACjC,6BAA6B,EAAE,CAAC;gBAEhC,mBAAmB,EAAE,CAAC;gBACtB,cAAc,EAAE,CAAC;gBACjB,kBAAkB,EAAE,CAAC;aACrB;SACD,CAAC,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACI,cAAc,CAAC,WAAmB,KAAK;QAC7C,IAAI,IAAI,CAAC,gBAAgB,KAAK,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,aAAa,EAAE,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACzE,CAAC;IAED;;OAEG;IACI,aAAa;QACnB,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC5C,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED;;OAEG;IACI,cAAc;QACpB,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,CAAC,SAAS,EAAE,CAAC;IAClB,CAAC;IAED;;OAEG;IACI,SAAS;QACf,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;YACzC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;IAED;;OAEG;IACc,oBAAoB,GAAG,CAAC,MAA0C,EAAQ,EAAE;QAC5F,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAAC;QAC7E,IAAI,QAAQ,EAAE,CAAC;YACd,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAA,gBAAI,EAAC,iDAAiD,CAAC,CAAC;YAC1G,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC;YAC/B,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC;YACnC,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC;YACrB,KAAK,CAAC,WAAW,CAAC,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YACvG,IAAI,IAAI,CAAC,gBAAgB,KAAK,SAAS,IAAI,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC,eAAe,EAAE,CAAC;gBAC1F,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,gBAAgB,CAAC;YAC/C,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,qBAAU,CAAC,OAAO,EAAE,CAAC;gBAC3C,KAAK,CAAC,eAAe,IAAI,CAAC,CAAC;gBAC3B,QAAQ,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;oBAC9B,KAAK,4CAAmB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;wBAC/C,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;wBACzB,IAAI,OAAO,CAAC,OAAO,CAAC,YAAY,KAAK,wCAAqB,CAAC,cAAc,EAAE,CAAC;4BAC3E,KAAK,CAAC,2BAA2B,IAAI,CAAC,CAAC;wBACxC,CAAC;6BAAM,IAAI,OAAO,CAAC,OAAO,CAAC,YAAY,KAAK,wCAAqB,CAAC,aAAa,EAAE,CAAC;4BACjF,KAAK,CAAC,4BAA4B,IAAI,CAAC,CAAC;wBACzC,CAAC;wBACD,MAAM;oBACP,CAAC;oBACD,KAAK,4CAAmB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;wBAC/C,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;wBACzB,QAAQ,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;4BACtC,KAAK,4CAAyB,CAAC,QAAQ;gCACtC,KAAK,CAAC,yBAAyB,IAAI,CAAC,CAAC;gCACrC,MAAM;4BACP,KAAK,4CAAyB,CAAC,uBAAuB;gCACrD,KAAK,CAAC,2CAA2C,IAAI,CAAC,CAAC;gCACvD,MAAM;4BACP;gCACC,qFAAqF;gCACrF,0FAA0F;gCAC1F,QAAQ,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;oCAC5C,KAAK,4CAAyB,CAAC,QAAQ;wCACtC,KAAK,CAAC,yBAAyB,IAAI,CAAC,CAAC;wCACrC,MAAM;oCACP;wCACC,MAAM;gCACR,CAAC;wBACH,CAAC;wBACD,MAAM;oBACP,CAAC;oBACD,KAAK,4CAAmB,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,CAAC;wBAC1D,KAAK,CAAC,wBAAwB,IAAI,CAAC,CAAC;wBACpC,QAAQ,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;4BACxC,KAAK,4CAAmB,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,CAAC;gCAC3D,KAAK,CAAC,6BAA6B,IAAI,CAAC,CAAC;4BAC1C,CAAC;4BACD,KAAK,4CAAmB,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC;gCAC5D,KAAK,CAAC,8BAA8B,IAAI,CAAC,CAAC;gCAC1C,MAAM;4BACP,CAAC;4BACD,KAAK,4CAAmB,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC;gCAC5D,KAAK,CAAC,8BAA8B,IAAI,CAAC,CAAC;gCAC1C,MAAM;4BACP,CAAC;4BACD,KAAK,4CAAmB,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,CAAC;gCAC3D,KAAK,CAAC,6BAA6B,IAAI,CAAC,CAAC;gCACzC,MAAM;4BACP,CAAC;4BACD,OAAO,CAAC,CAAC,CAAC;gCACT,oGAAoG;gCACpG,MAAM,CAAC,GAAU,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;4BAC5C,CAAC;wBACF,CAAC;wBACD,MAAM;oBACP,CAAC;oBACD,KAAK,4CAAmB,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC;wBACrD,KAAK,CAAC,mBAAmB,IAAI,CAAC,CAAC;wBAC/B,MAAM;oBACP,CAAC;oBACD,KAAK,4CAAmB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;wBAChD,KAAK,CAAC,cAAc,IAAI,CAAC,CAAC;wBAC1B,MAAM;oBACP,CAAC;oBACD,KAAK,4CAAmB,CAAC,WAAW,CAAC,8BAA8B,CAAC;oBACpE,KAAK,4CAAmB,CAAC,WAAW,CAAC,wBAAwB,CAAC;oBAC9D,KAAK,4CAAmB,CAAC,WAAW,CAAC,kBAAkB,CAAC;oBACxD,KAAK,4CAAmB,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAC;wBAC7D,KAAK,CAAC,kBAAkB,IAAI,CAAC,CAAC;wBAC9B,MAAM;oBACP,CAAC;oBACD,OAAO,CAAC,CAAC,CAAC;wBACT,oGAAoG;wBACpG,MAAM,CAAC,GAAU,OAAO,CAAC,OAAO,CAAC;oBAClC,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC,CAAC;IAEF;;OAEG;IACc,YAAY,GAAG,GAAS,EAAE;QAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvD,IAAI,MAAM,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;gBACnC,gGAAgG;gBAChG,MAAM,CAAC,IAAI,CACV;oBACC,QAAQ,EAAE,WAAW;oBACrB,SAAS,EAAE,iBAAiB;oBAC5B,GAAG,KAAK;oBACR,qDAAqD;oBACrD,6CAA6C;oBAC7C,iFAAiF;oBACjF,WAAW,EAAE,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC;iBACjD,EACD,0BAAQ,CAAC,SAAS,CAClB,CAAC;gBACF,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;QACF,CAAC;IACF,CAAC,CAAC;CACF;AAjPD,0FAiPC;AAED,SAAS,iBAAiB,CAAC,OAA0B;IACpD,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;SAC5B,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;SAC9C,IAAI,CAAC,GAAG,CAAC,CAAC;AACb,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { LogLevel } from '@fluidframework/core-interfaces';\nimport type { ITelemetryBaseLogger } from '@fluidframework/core-interfaces';\n\nimport { assertNotUndefined, fail } from './Common.js';\nimport { PlaceValidationResult, RangeValidationResultKind } from './EditUtilities.js';\nimport { SharedTreeEvent } from './EventTypes.js';\nimport { SequencedEditAppliedEventArguments, SharedTree } from './SharedTree.js';\nimport { TransactionInternal } from './TransactionInternal.js';\nimport { EditStatus } from './persisted-types/index.js';\n\n/**\n * Logs generic telemetry for failed sequenced edits.\n * Only failing edits that were originally made locally are logged.\n * @param tree - The tree for which to log the telemetry.\n * @internal\n */\nexport function useFailedSequencedEditTelemetry(tree: SharedTree): { disable: () => void } {\n\tfunction onEdit({ wasLocal, logger, outcome }: SequencedEditAppliedEventArguments): void {\n\t\tif (wasLocal && outcome.status !== EditStatus.Applied) {\n\t\t\tlogger.send(\n\t\t\t\t{\n\t\t\t\t\tcategory: 'generic',\n\t\t\t\t\teventName: outcome.status === EditStatus.Malformed ? 'MalformedSharedTreeEdit' : 'InvalidSharedTreeEdit',\n\t\t\t\t},\n\t\t\t\tLogLevel.essential\n\t\t\t);\n\t\t}\n\t}\n\ttree.on(SharedTreeEvent.SequencedEditApplied, onEdit);\n\treturn {\n\t\tdisable: () => {\n\t\t\ttree.off(SharedTreeEvent.SequencedEditApplied, onEdit);\n\t\t},\n\t};\n}\n\n/**\n * Statistics about the health of collaborative edit merging when using {@link SharedTree}.\n * All of those numbers constitute a tally since the last heartbeat was logged or cleared.\n * @internal\n */\nexport interface MergeHealthStats {\n\t/** Number of sequenced edits applied (failed or not). */\n\teditCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply.\n\t * Such cases are also counted under {@link MergeHealthStats.editCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.badPlaceCount}\n\t * * {@link MergeHealthStats.badRangeCount}\n\t * * {@link MergeHealthStats.constraintViolationCount}\n\t * * {@link MergeHealthStats.idAlreadyInUseCount}\n\t * * {@link MergeHealthStats.unknownIdCount}\n\t * * {@link MergeHealthStats.malformedEditCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tfailedEditCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a bad place.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.deletedAncestorBadPlaceCount}\n\t * * {@link MergeHealthStats.deletedSiblingBadPlaceCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tbadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a bad range.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.deletedAncestorBadRangeCount}\n\t * * {@link MergeHealthStats.deletedSiblingBadRangeCount}\n\t * * {@link MergeHealthStats.updatedRangeInvertedCount}\n\t * * {@link MergeHealthStats.updatedRangeHasPlacesInDifferentTraitsCount}\n\t * * {@link MergeHealthStats.updatedRangeBadPlaceCount}\n\t * * {@link MergeHealthStats.updatedRangeNeverValidPlaceCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tbadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a place whose ancestors had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badPlaceCount}.\n\t */\n\tdeletedAncestorBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose ancestors had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tdeletedAncestorBadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a place whose sibling (but not its parent) had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badPlaceCount}.\n\t */\n\tdeletedSiblingBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose delimiting sibling(s) (but not its parent) had been concurrently\n\t * deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tdeletedSiblingBadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but inverted (i.e., end before start).\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeInvertedCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but in different traits.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeHasPlacesInDifferentTraitsCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but bad.\n\t * This should not happen because place resolution is expected to either return a valid place or fail.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were not resolvable due to not being valid for any state.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeNeverValidPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constraint violation.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.rangeConstraintViolationCount}\n\t * * {@link MergeHealthStats.lengthConstraintViolationCount}\n\t * * {@link MergeHealthStats.parentConstraintViolationCount}\n\t * * {@link MergeHealthStats.labelConstraintViolationCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tconstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range becoming invalid or malformed.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\trangeConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range having a different length.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tlengthConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range being under a different parent.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tparentConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range being under a different label.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tlabelConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an ID collision.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tidAlreadyInUseCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an ID being unknown.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tunknownIdCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an edit becoming malformed.\n\t * This should theoretically never happen.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tmalformedEditCount: number;\n\n\t/**\n\t * The counts of occurrences for a given path length. `pathLengths[1] === 2` means two occurrences of length one.\n\t */\n\tpathLengths: number[];\n\n\t/** The highest number previous attempts on a sequenced edit. */\n\tmaxAttemptCount: number;\n}\n\n/**\n * Aggregates and logs telemetry about the success of concurrent edits.\n * @internal\n */\nexport class SharedTreeMergeHealthTelemetryHeartbeat {\n\tprivate heartbeatTimerId = 0;\n\tprivate readonly treeData = new Map<SharedTree, { tally: MergeHealthStats; logger?: ITelemetryBaseLogger }>();\n\n\t/**\n\t * Adds a tree to the set of tree to log merge health telemetry for.\n\t * Noop if such a tree was already in the set.\n\t * @param tree - The tree to log merge health telemetry for.\n\t */\n\tpublic attachTree(tree: SharedTree): void {\n\t\tif (this.treeData.has(tree) === false) {\n\t\t\tthis.resetTreeData(tree);\n\t\t\ttree.on(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);\n\t\t}\n\t}\n\n\t/**\n\t * Removes a tree from the set of tree to log merge health telemetry for.\n\t * Noop if such a tree was never in the set.\n\t * @param tree - The tree to stop logging merge health telemetry for.\n\t */\n\tpublic detachTree(tree: SharedTree): void {\n\t\tif (this.treeData.has(tree)) {\n\t\t\ttree.off(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);\n\t\t\tthis.treeData.delete(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Exposes the aggregated statistics about merge health for the given tree.\n\t * @param tree - The tree to get stats for.\n\t * @returns Aggregated statistics about merge health for the given tree.\n\t */\n\tpublic getStats(tree: SharedTree): MergeHealthStats {\n\t\treturn assertNotUndefined(this.treeData.get(tree), 'No such tree was attached to the logger').tally;\n\t}\n\n\t/**\n\t * Removes all trees from the set of tree to log merge health telemetry for.\n\t */\n\tpublic detachAllTrees(): void {\n\t\tfor (const tree of this.treeData.keys()) {\n\t\t\tthis.detachTree(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Resets the aggregated merge health data for the given tree.\n\t * @param tree - The tree to reset the merge health data for.\n\t */\n\tpublic resetTreeData(tree: SharedTree): void {\n\t\tthis.treeData.set(tree, {\n\t\t\ttally: {\n\t\t\t\tmaxAttemptCount: 0,\n\t\t\t\tpathLengths: [],\n\t\t\t\teditCount: 0,\n\t\t\t\tfailedEditCount: 0,\n\t\t\t\tbadPlaceCount: 0,\n\t\t\t\tbadRangeCount: 0,\n\n\t\t\t\tdeletedAncestorBadPlaceCount: 0,\n\t\t\t\tdeletedAncestorBadRangeCount: 0,\n\n\t\t\t\tdeletedSiblingBadPlaceCount: 0,\n\t\t\t\tdeletedSiblingBadRangeCount: 0,\n\t\t\t\tupdatedRangeInvertedCount: 0,\n\t\t\t\tupdatedRangeHasPlacesInDifferentTraitsCount: 0,\n\t\t\t\tupdatedRangeBadPlaceCount: 0,\n\t\t\t\tupdatedRangeNeverValidPlaceCount: 0,\n\n\t\t\t\tconstraintViolationCount: 0,\n\t\t\t\trangeConstraintViolationCount: 0,\n\t\t\t\tlengthConstraintViolationCount: 0,\n\t\t\t\tparentConstraintViolationCount: 0,\n\t\t\t\tlabelConstraintViolationCount: 0,\n\n\t\t\t\tidAlreadyInUseCount: 0,\n\t\t\t\tunknownIdCount: 0,\n\t\t\t\tmalformedEditCount: 0,\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Enables the regular telemetry logging of merge health data.\n\t * The first message will be sent after `interval` milliseconds. See {@link SharedTreeMergeHealthTelemetryHeartbeat.flushHeartbeat} for\n\t * immediate logging.\n\t * @param interval - The amount of time in milliseconds between log messages.\n\t */\n\tpublic startHeartbeat(interval: number = 60000): void {\n\t\tif (this.heartbeatTimerId !== 0) {\n\t\t\tthis.stopHeartbeat();\n\t\t}\n\t\tthis.heartbeatTimerId = window.setInterval(this.logHeartbeat, interval);\n\t}\n\n\t/**\n\t * Disables the regular telemetry logging of merge health data.\n\t */\n\tpublic stopHeartbeat(): void {\n\t\twindow.clearInterval(this.heartbeatTimerId);\n\t\tthis.heartbeatTimerId = 0;\n\t}\n\n\t/**\n\t * Sends all collected merge health data and resets the aggregated state.\n\t */\n\tpublic flushHeartbeat(): void {\n\t\tthis.logHeartbeat();\n\t\tthis.clearData();\n\t}\n\n\t/**\n\t * Resets all aggregated state.\n\t */\n\tpublic clearData(): void {\n\t\tfor (const tree of this.treeData.keys()) {\n\t\t\tthis.resetTreeData(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Receives SequencedEditApplied events from trees.\n\t */\n\tprivate readonly sequencedEditHandler = (params: SequencedEditAppliedEventArguments): void => {\n\t\tconst { edit, tree, wasLocal, logger, outcome, reconciliationPath } = params;\n\t\tif (wasLocal) {\n\t\t\tconst tallyAndLogger = this.treeData.get(tree) ?? fail('Should only receive events for registered trees');\n\t\t\ttallyAndLogger.logger = logger;\n\t\t\tconst tally = tallyAndLogger.tally;\n\t\t\ttally.editCount += 1;\n\t\t\ttally.pathLengths[reconciliationPath.length] = (tally.pathLengths[reconciliationPath.length] ?? 0) + 1;\n\t\t\tif (edit.pastAttemptCount !== undefined && edit.pastAttemptCount > tally.maxAttemptCount) {\n\t\t\t\ttally.maxAttemptCount = edit.pastAttemptCount;\n\t\t\t}\n\t\t\tif (outcome.status !== EditStatus.Applied) {\n\t\t\t\ttally.failedEditCount += 1;\n\t\t\t\tswitch (outcome.failure.kind) {\n\t\t\t\t\tcase TransactionInternal.FailureKind.BadPlace: {\n\t\t\t\t\t\ttally.badPlaceCount += 1;\n\t\t\t\t\t\tif (outcome.failure.placeFailure === PlaceValidationResult.MissingSibling) {\n\t\t\t\t\t\t\ttally.deletedSiblingBadPlaceCount += 1;\n\t\t\t\t\t\t} else if (outcome.failure.placeFailure === PlaceValidationResult.MissingParent) {\n\t\t\t\t\t\t\ttally.deletedAncestorBadPlaceCount += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.BadRange: {\n\t\t\t\t\t\ttally.badRangeCount += 1;\n\t\t\t\t\t\tswitch (outcome.failure.rangeFailure) {\n\t\t\t\t\t\t\tcase RangeValidationResultKind.Inverted:\n\t\t\t\t\t\t\t\ttally.updatedRangeInvertedCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase RangeValidationResultKind.PlacesInDifferentTraits:\n\t\t\t\t\t\t\t\ttally.updatedRangeHasPlacesInDifferentTraitsCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t// 'rangeFailure' is either a RangeValidationResultKind (handled above), or an object\n\t\t\t\t\t\t\t\t// with a nested 'kind' property containing the RangeValidationResultKind (handled below).\n\t\t\t\t\t\t\t\tswitch (outcome.failure.rangeFailure?.kind) {\n\t\t\t\t\t\t\t\t\tcase RangeValidationResultKind.BadPlace:\n\t\t\t\t\t\t\t\t\t\ttally.updatedRangeBadPlaceCount += 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.ConstraintViolation: {\n\t\t\t\t\t\ttally.constraintViolationCount += 1;\n\t\t\t\t\t\tswitch (outcome.failure.violation.kind) {\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadRange: {\n\t\t\t\t\t\t\t\ttally.rangeConstraintViolationCount += 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadLength: {\n\t\t\t\t\t\t\t\ttally.lengthConstraintViolationCount += 1;\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 TransactionInternal.ConstraintViolationKind.BadParent: {\n\t\t\t\t\t\t\t\ttally.parentConstraintViolationCount += 1;\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 TransactionInternal.ConstraintViolationKind.BadLabel: {\n\t\t\t\t\t\t\t\ttally.labelConstraintViolationCount += 1;\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\t// If this doesn't type-check, the above switch statement needs to be extended to handle a new case.\n\t\t\t\t\t\t\t\tconst _: never = outcome.failure.violation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.IdAlreadyInUse: {\n\t\t\t\t\t\ttally.idAlreadyInUseCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.UnknownId: {\n\t\t\t\t\t\ttally.unknownIdCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.DetachedSequenceIdAlreadyInUse:\n\t\t\t\t\tcase TransactionInternal.FailureKind.DetachedSequenceNotFound:\n\t\t\t\t\tcase TransactionInternal.FailureKind.DuplicateIdInBuild:\n\t\t\t\t\tcase TransactionInternal.FailureKind.UnusedDetachedSequence: {\n\t\t\t\t\t\ttally.malformedEditCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\t// If this doesn't type-check, the above switch statement needs to be extended to handle a new case.\n\t\t\t\t\t\tconst _: never = outcome.failure;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Logs the accumulated merge health data to each tree's designated logger.\n\t */\n\tprivate readonly logHeartbeat = (): void => {\n\t\tfor (const [tree, { tally, logger }] of this.treeData) {\n\t\t\tif (logger && tally.editCount > 0) {\n\t\t\t\t// Note: all this data is for sequenced edits that were originally produced by the local client.\n\t\t\t\tlogger.send(\n\t\t\t\t\t{\n\t\t\t\t\t\tcategory: 'Heartbeat',\n\t\t\t\t\t\teventName: 'EditMergeHealth',\n\t\t\t\t\t\t...tally,\n\t\t\t\t\t\t// The counts of occurrences for a given path length.\n\t\t\t\t\t\t// '1:2' means two occurrences of length one.\n\t\t\t\t\t\t// Overwrites `tally.pathLengths` which is incompatible with ITelemetryBaseEvent.\n\t\t\t\t\t\tpathLengths: pathLengthsCounts(tally.pathLengths),\n\t\t\t\t\t},\n\t\t\t\t\tLogLevel.essential\n\t\t\t\t);\n\t\t\t\tthis.resetTreeData(tree);\n\t\t\t}\n\t\t}\n\t};\n}\n\nfunction pathLengthsCounts(lengths: readonly number[]): string {\n\treturn Object.entries(lengths)\n\t\t.map(([length, count]) => `${length}:${count}`)\n\t\t.join(',');\n}\n"]}
@@ -4,7 +4,10 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.getNodeId = exports.getNodeIdContext = exports.sequencedIdNormalizer = exports.scopeIdNormalizer = void 0;
7
+ exports.scopeIdNormalizer = scopeIdNormalizer;
8
+ exports.sequencedIdNormalizer = sequencedIdNormalizer;
9
+ exports.getNodeIdContext = getNodeIdContext;
10
+ exports.getNodeId = getNodeId;
8
11
  const Common_js_1 = require("./Common.js");
9
12
  const index_js_1 = require("./id-compressor/index.js");
10
13
  /**
@@ -17,7 +20,6 @@ function scopeIdNormalizer(idNormalizer, sessionId) {
17
20
  normalizeToSessionSpace: (id) => idNormalizer.normalizeToSessionSpace(id, sessionId ?? idNormalizer.localSessionId),
18
21
  };
19
22
  }
20
- exports.scopeIdNormalizer = scopeIdNormalizer;
21
23
  /**
22
24
  * Create a {@link ContextualizedNodeIdNormalizer} that uses the local session ID belonging to the normalizer
23
25
  * to normalize IDs to session space. These IDs are expected to be sequenced, and will fail to normalize if
@@ -37,7 +39,6 @@ function sequencedIdNormalizer(idNormalizer) {
37
39
  },
38
40
  };
39
41
  }
40
- exports.sequencedIdNormalizer = sequencedIdNormalizer;
41
42
  function getNodeIdContext(compressor) {
42
43
  return {
43
44
  generateNodeId: (override) => compressor.generateCompressedId(override),
@@ -50,10 +51,8 @@ function getNodeIdContext(compressor) {
50
51
  localSessionId: compressor.localSessionId,
51
52
  };
52
53
  }
53
- exports.getNodeIdContext = getNodeIdContext;
54
54
  /** Accepts either a node or a node's identifier, and returns the identifier */
55
55
  function getNodeId(node) {
56
56
  return node.identifier ?? node;
57
57
  }
58
- exports.getNodeId = getNodeId;
59
58
  //# sourceMappingURL=NodeIdUtilities.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodeIdUtilities.js","sourceRoot":"","sources":["../src/NodeIdUtilities.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,2CAAgD;AAEhD,uDAAmE;AA8FnE;;;GAGG;AACH,SAAgB,iBAAiB,CAChC,YAAmC,EACnC,SAAqB;IAErB,OAAO;QACN,kBAAkB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC/D,uBAAuB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,uBAAuB,CAAC,EAAE,EAAE,SAAS,IAAI,YAAY,CAAC,cAAc,CAAC;KACnH,CAAC;AACH,CAAC;AARD,8CAQC;AAED;;;;GAIG;AACH,SAAgB,qBAAqB,CACpC,YAAmC;IAEnC,OAAO;QACN,kBAAkB,EAAE,CAAC,EAAE,EAAE,EAAE;YAC1B,MAAM,UAAU,GAAG,YAAY,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;YACvD,IAAA,6BAAiB,EAAC,IAAA,oBAAS,EAAC,UAAU,CAAC,CAAC,CAAC;YACzC,OAAO,UAAU,CAAC;QACnB,CAAC;QACD,uBAAuB,EAAE,CAAC,EAAE,EAAE,EAAE;YAC/B,MAAM,UAAU,GAAG,YAAY,CAAC,uBAAuB,CAAC,EAAE,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC;YACzF,IAAA,6BAAiB,EAAC,IAAA,oBAAS,EAAC,UAAU,CAAC,CAAC,CAAC;YACzC,OAAO,UAAU,CAAC;QACnB,CAAC;KACD,CAAC;AACH,CAAC;AAfD,sDAeC;AAED,SAAgB,gBAAgB,CAAC,UAAwB;IACxD,OAAO;QACN,cAAc,EAAE,CAAC,QAAiB,EAAE,EAAE,CAAC,UAAU,CAAC,oBAAoB,CAAC,QAAQ,CAAW;QAC1F,eAAe,EAAE,CAAC,EAAgB,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAW;QAC1E,kBAAkB,EAAE,CAAC,EAAgB,EAAE,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAuB;QAC5F,qBAAqB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAiB;QAChF,wBAAwB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAiB;QACtF,kBAAkB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAkB;QACtF,uBAAuB,EAAE,CAAC,EAAiB,EAAE,SAAoB,EAAE,EAAE,CACpE,UAAU,CAAC,uBAAuB,CAAC,EAAE,EAAE,SAAS,CAAW;QAC5D,cAAc,EAAE,UAAU,CAAC,cAAc;KACzC,CAAC;AACH,CAAC;AAZD,4CAYC;AAED,+EAA+E;AAC/E,SAAgB,SAAS,CAA8B,IAAyB;IAC/E,OAAQ,IAAsB,CAAC,UAAU,IAAK,IAAY,CAAC;AAC5D,CAAC;AAFD,8BAEC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assertWithMessage } from './Common.js';\nimport { FinalNodeId, NodeId, OpSpaceNodeId, SessionId, StableNodeId } from './Identifiers.js';\nimport { IdCompressor, isFinalId } from './id-compressor/index.js';\nimport { NodeData } from './persisted-types/index.js';\n\n/**\n * An object which can generate node IDs and convert node IDs between compressed and stable variants\n * @alpha\n */\nexport interface NodeIdContext extends NodeIdGenerator, NodeIdConverter {}\n\n/**\n * An object which can generate node IDs\n * @alpha\n */\nexport interface NodeIdGenerator {\n\t/**\n\t * Generates a node identifier.\n\t * The returned IDs may be used as the identifier of a node in the SharedTree.\n\t * `NodeId`s are *always* unique and stable within the scope of the tree and session that generated them. They are *not* unique within\n\t * a Fluid container, and *cannot* be compared across instances of a SharedTree. They are *not* stable across sessions/lifetimes of a\n\t * SharedTree, and *cannot* be persisted (e.g. stored in payloads, uploaded in blobs, etc.). If stable persistence is needed,\n\t * NodeIdConverter.convertToStableNodeId may be used to return a corresponding UUID that is globally unique and stable.\n\t * @param override - if supplied, calls to `convertToStableNodeId` using the returned node ID will return the override instead of\n\t * the UUID. Calls to `generateNodeId` with the same override always return the same ID. Performance note: passing an override string\n\t * incurs a storage cost that is significantly higher that a node ID without one, and should be avoided if possible.\n\t */\n\tgenerateNodeId(override?: string): NodeId;\n}\n\n/**\n * An object which can convert node IDs between compressed and stable variants\n * @alpha\n */\nexport interface NodeIdConverter {\n\t/**\n\t * Given a NodeId, returns the corresponding stable ID or throws if the supplied node ID was not generated with this tree (`NodeId`s\n\t * may not be used across SharedTree instances, see `generateNodeId` for more).\n\t * The returned value will be a UUID, unless the creation of `id` used an override string (see `generateNodeId` for more).\n\t * The result is safe to persist and re-use across `SharedTree` instances, unlike `NodeId`.\n\t */\n\tconvertToStableNodeId(id: NodeId): StableNodeId;\n\n\t/**\n\t * Given a NodeId, attempt to return the corresponding stable ID.\n\t * The returned value will be a UUID, unless the creation of `id` used an override string (see `generateNodeId` for more).\n\t * The returned stable ID is undefined if `id` was never created with this SharedTree. If a stable ID is returned, this does not imply\n\t * that there is a node with `id` in the current revision of the tree, only that `id` was at some point generated by some instance of\n\t * this tree.\n\t */\n\ttryConvertToStableNodeId(id: NodeId): StableNodeId | undefined;\n\n\t/**\n\t * Given a stable ID, return the corresponding NodeId or throws if the supplied stable ID was never generated with this tree, either\n\t * as a UUID corresponding to a `NodeId` or as an override passed to `generateNodeId`.\n\t * If a stable ID is returned, this does not imply that there is a node with `id` in the current revision of the tree, only that\n\t * `id` was at some point generated by an instance of this SharedTree.\n\t */\n\tconvertToNodeId(id: StableNodeId): NodeId;\n\n\t/**\n\t * Given a stable ID, return the corresponding NodeId or return undefined if the supplied stable ID was never generated with this tree,\n\t * either as a UUID corresponding to a `NodeId` or as an override passed to `generateNodeId`.\n\t * If a stable ID is returned, this does not imply that there is a node with `id` in the current revision of the tree, only that\n\t * `id` was at some point generated by an instance of this SharedTree.\n\t */\n\ttryConvertToNodeId(id: StableNodeId): NodeId | undefined;\n}\n\n/**\n * An object which can normalize node IDs. See docs on {@link IdCompressor} for semantics of normalization.\n */\nexport interface NodeIdNormalizer<TId extends OpSpaceNodeId> {\n\tlocalSessionId: SessionId;\n\t/**\n\t * Normalizes the given ID to op space\n\t */\n\tnormalizeToOpSpace(id: NodeId): TId;\n\t/**\n\t * Normalizes the given ID to session space\n\t */\n\tnormalizeToSessionSpace(id: TId, sessionId: SessionId): NodeId;\n}\n\n/**\n * An object which can normalize node IDs. It is contextualized to a known session context, and therefore\n * can normalize IDs into session space without requiring any additional information.\n */\nexport interface ContextualizedNodeIdNormalizer<TId extends OpSpaceNodeId>\n\textends Omit<NodeIdNormalizer<TId>, 'localSessionId' | 'normalizeToSessionSpace'> {\n\t/**\n\t * Normalizes the given ID to session space\n\t */\n\tnormalizeToSessionSpace(id: TId): NodeId;\n}\n\n/**\n * Create a {@link ContextualizedNodeIdNormalizer} that uses either the given session ID to normalize IDs\n * to session space. If no ID is given, it will use the local session ID belonging to the normalizer.\n */\nexport function scopeIdNormalizer<TId extends OpSpaceNodeId>(\n\tidNormalizer: NodeIdNormalizer<TId>,\n\tsessionId?: SessionId\n): ContextualizedNodeIdNormalizer<TId> {\n\treturn {\n\t\tnormalizeToOpSpace: (id) => idNormalizer.normalizeToOpSpace(id),\n\t\tnormalizeToSessionSpace: (id) => idNormalizer.normalizeToSessionSpace(id, sessionId ?? idNormalizer.localSessionId),\n\t};\n}\n\n/**\n * Create a {@link ContextualizedNodeIdNormalizer} that uses the local session ID belonging to the normalizer\n * to normalize IDs to session space. These IDs are expected to be sequenced, and will fail to normalize if\n * they are not.\n */\nexport function sequencedIdNormalizer<TId extends OpSpaceNodeId>(\n\tidNormalizer: NodeIdNormalizer<TId>\n): ContextualizedNodeIdNormalizer<FinalNodeId & TId> {\n\treturn {\n\t\tnormalizeToOpSpace: (id) => {\n\t\t\tconst normalized = idNormalizer.normalizeToOpSpace(id);\n\t\t\tassertWithMessage(isFinalId(normalized));\n\t\t\treturn normalized;\n\t\t},\n\t\tnormalizeToSessionSpace: (id) => {\n\t\t\tconst normalized = idNormalizer.normalizeToSessionSpace(id, idNormalizer.localSessionId);\n\t\t\tassertWithMessage(isFinalId(normalized));\n\t\t\treturn normalized;\n\t\t},\n\t};\n}\n\nexport function getNodeIdContext(compressor: IdCompressor): NodeIdContext & NodeIdNormalizer<OpSpaceNodeId> {\n\treturn {\n\t\tgenerateNodeId: (override?: string) => compressor.generateCompressedId(override) as NodeId,\n\t\tconvertToNodeId: (id: StableNodeId) => compressor.recompress(id) as NodeId,\n\t\ttryConvertToNodeId: (id: StableNodeId) => compressor.tryRecompress(id) as NodeId | undefined,\n\t\tconvertToStableNodeId: (id: NodeId) => compressor.decompress(id) as StableNodeId,\n\t\ttryConvertToStableNodeId: (id: NodeId) => compressor.tryDecompress(id) as StableNodeId,\n\t\tnormalizeToOpSpace: (id: NodeId) => compressor.normalizeToOpSpace(id) as OpSpaceNodeId,\n\t\tnormalizeToSessionSpace: (id: OpSpaceNodeId, sessionId: SessionId) =>\n\t\t\tcompressor.normalizeToSessionSpace(id, sessionId) as NodeId,\n\t\tlocalSessionId: compressor.localSessionId,\n\t};\n}\n\n/** Accepts either a node or a node's identifier, and returns the identifier */\nexport function getNodeId<TId extends number | string>(node: TId | NodeData<TId>): TId {\n\treturn (node as NodeData<TId>).identifier ?? (node as TId);\n}\n"]}
1
+ {"version":3,"file":"NodeIdUtilities.js","sourceRoot":"","sources":["../src/NodeIdUtilities.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAsGH,8CAQC;AAOD,sDAeC;AAED,4CAYC;AAGD,8BAEC;AArJD,2CAAgD;AAEhD,uDAAmE;AA8FnE;;;GAGG;AACH,SAAgB,iBAAiB,CAChC,YAAmC,EACnC,SAAqB;IAErB,OAAO;QACN,kBAAkB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC/D,uBAAuB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,uBAAuB,CAAC,EAAE,EAAE,SAAS,IAAI,YAAY,CAAC,cAAc,CAAC;KACnH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,qBAAqB,CACpC,YAAmC;IAEnC,OAAO;QACN,kBAAkB,EAAE,CAAC,EAAE,EAAE,EAAE;YAC1B,MAAM,UAAU,GAAG,YAAY,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;YACvD,IAAA,6BAAiB,EAAC,IAAA,oBAAS,EAAC,UAAU,CAAC,CAAC,CAAC;YACzC,OAAO,UAAU,CAAC;QACnB,CAAC;QACD,uBAAuB,EAAE,CAAC,EAAE,EAAE,EAAE;YAC/B,MAAM,UAAU,GAAG,YAAY,CAAC,uBAAuB,CAAC,EAAE,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC;YACzF,IAAA,6BAAiB,EAAC,IAAA,oBAAS,EAAC,UAAU,CAAC,CAAC,CAAC;YACzC,OAAO,UAAU,CAAC;QACnB,CAAC;KACD,CAAC;AACH,CAAC;AAED,SAAgB,gBAAgB,CAAC,UAAwB;IACxD,OAAO;QACN,cAAc,EAAE,CAAC,QAAiB,EAAE,EAAE,CAAC,UAAU,CAAC,oBAAoB,CAAC,QAAQ,CAAW;QAC1F,eAAe,EAAE,CAAC,EAAgB,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAW;QAC1E,kBAAkB,EAAE,CAAC,EAAgB,EAAE,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAuB;QAC5F,qBAAqB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAiB;QAChF,wBAAwB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAiB;QACtF,kBAAkB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAkB;QACtF,uBAAuB,EAAE,CAAC,EAAiB,EAAE,SAAoB,EAAE,EAAE,CACpE,UAAU,CAAC,uBAAuB,CAAC,EAAE,EAAE,SAAS,CAAW;QAC5D,cAAc,EAAE,UAAU,CAAC,cAAc;KACzC,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,SAAgB,SAAS,CAA8B,IAAyB;IAC/E,OAAQ,IAAsB,CAAC,UAAU,IAAK,IAAY,CAAC;AAC5D,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assertWithMessage } from './Common.js';\nimport { FinalNodeId, NodeId, OpSpaceNodeId, SessionId, StableNodeId } from './Identifiers.js';\nimport { IdCompressor, isFinalId } from './id-compressor/index.js';\nimport { NodeData } from './persisted-types/index.js';\n\n/**\n * An object which can generate node IDs and convert node IDs between compressed and stable variants\n * @alpha\n */\nexport interface NodeIdContext extends NodeIdGenerator, NodeIdConverter {}\n\n/**\n * An object which can generate node IDs\n * @alpha\n */\nexport interface NodeIdGenerator {\n\t/**\n\t * Generates a node identifier.\n\t * The returned IDs may be used as the identifier of a node in the SharedTree.\n\t * `NodeId`s are *always* unique and stable within the scope of the tree and session that generated them. They are *not* unique within\n\t * a Fluid container, and *cannot* be compared across instances of a SharedTree. They are *not* stable across sessions/lifetimes of a\n\t * SharedTree, and *cannot* be persisted (e.g. stored in payloads, uploaded in blobs, etc.). If stable persistence is needed,\n\t * NodeIdConverter.convertToStableNodeId may be used to return a corresponding UUID that is globally unique and stable.\n\t * @param override - if supplied, calls to `convertToStableNodeId` using the returned node ID will return the override instead of\n\t * the UUID. Calls to `generateNodeId` with the same override always return the same ID. Performance note: passing an override string\n\t * incurs a storage cost that is significantly higher that a node ID without one, and should be avoided if possible.\n\t */\n\tgenerateNodeId(override?: string): NodeId;\n}\n\n/**\n * An object which can convert node IDs between compressed and stable variants\n * @alpha\n */\nexport interface NodeIdConverter {\n\t/**\n\t * Given a NodeId, returns the corresponding stable ID or throws if the supplied node ID was not generated with this tree (`NodeId`s\n\t * may not be used across SharedTree instances, see `generateNodeId` for more).\n\t * The returned value will be a UUID, unless the creation of `id` used an override string (see `generateNodeId` for more).\n\t * The result is safe to persist and re-use across `SharedTree` instances, unlike `NodeId`.\n\t */\n\tconvertToStableNodeId(id: NodeId): StableNodeId;\n\n\t/**\n\t * Given a NodeId, attempt to return the corresponding stable ID.\n\t * The returned value will be a UUID, unless the creation of `id` used an override string (see `generateNodeId` for more).\n\t * The returned stable ID is undefined if `id` was never created with this SharedTree. If a stable ID is returned, this does not imply\n\t * that there is a node with `id` in the current revision of the tree, only that `id` was at some point generated by some instance of\n\t * this tree.\n\t */\n\ttryConvertToStableNodeId(id: NodeId): StableNodeId | undefined;\n\n\t/**\n\t * Given a stable ID, return the corresponding NodeId or throws if the supplied stable ID was never generated with this tree, either\n\t * as a UUID corresponding to a `NodeId` or as an override passed to `generateNodeId`.\n\t * If a stable ID is returned, this does not imply that there is a node with `id` in the current revision of the tree, only that\n\t * `id` was at some point generated by an instance of this SharedTree.\n\t */\n\tconvertToNodeId(id: StableNodeId): NodeId;\n\n\t/**\n\t * Given a stable ID, return the corresponding NodeId or return undefined if the supplied stable ID was never generated with this tree,\n\t * either as a UUID corresponding to a `NodeId` or as an override passed to `generateNodeId`.\n\t * If a stable ID is returned, this does not imply that there is a node with `id` in the current revision of the tree, only that\n\t * `id` was at some point generated by an instance of this SharedTree.\n\t */\n\ttryConvertToNodeId(id: StableNodeId): NodeId | undefined;\n}\n\n/**\n * An object which can normalize node IDs. See docs on {@link IdCompressor} for semantics of normalization.\n */\nexport interface NodeIdNormalizer<TId extends OpSpaceNodeId> {\n\tlocalSessionId: SessionId;\n\t/**\n\t * Normalizes the given ID to op space\n\t */\n\tnormalizeToOpSpace(id: NodeId): TId;\n\t/**\n\t * Normalizes the given ID to session space\n\t */\n\tnormalizeToSessionSpace(id: TId, sessionId: SessionId): NodeId;\n}\n\n/**\n * An object which can normalize node IDs. It is contextualized to a known session context, and therefore\n * can normalize IDs into session space without requiring any additional information.\n */\nexport interface ContextualizedNodeIdNormalizer<TId extends OpSpaceNodeId>\n\textends Omit<NodeIdNormalizer<TId>, 'localSessionId' | 'normalizeToSessionSpace'> {\n\t/**\n\t * Normalizes the given ID to session space\n\t */\n\tnormalizeToSessionSpace(id: TId): NodeId;\n}\n\n/**\n * Create a {@link ContextualizedNodeIdNormalizer} that uses either the given session ID to normalize IDs\n * to session space. If no ID is given, it will use the local session ID belonging to the normalizer.\n */\nexport function scopeIdNormalizer<TId extends OpSpaceNodeId>(\n\tidNormalizer: NodeIdNormalizer<TId>,\n\tsessionId?: SessionId\n): ContextualizedNodeIdNormalizer<TId> {\n\treturn {\n\t\tnormalizeToOpSpace: (id) => idNormalizer.normalizeToOpSpace(id),\n\t\tnormalizeToSessionSpace: (id) => idNormalizer.normalizeToSessionSpace(id, sessionId ?? idNormalizer.localSessionId),\n\t};\n}\n\n/**\n * Create a {@link ContextualizedNodeIdNormalizer} that uses the local session ID belonging to the normalizer\n * to normalize IDs to session space. These IDs are expected to be sequenced, and will fail to normalize if\n * they are not.\n */\nexport function sequencedIdNormalizer<TId extends OpSpaceNodeId>(\n\tidNormalizer: NodeIdNormalizer<TId>\n): ContextualizedNodeIdNormalizer<FinalNodeId & TId> {\n\treturn {\n\t\tnormalizeToOpSpace: (id) => {\n\t\t\tconst normalized = idNormalizer.normalizeToOpSpace(id);\n\t\t\tassertWithMessage(isFinalId(normalized));\n\t\t\treturn normalized;\n\t\t},\n\t\tnormalizeToSessionSpace: (id) => {\n\t\t\tconst normalized = idNormalizer.normalizeToSessionSpace(id, idNormalizer.localSessionId);\n\t\t\tassertWithMessage(isFinalId(normalized));\n\t\t\treturn normalized;\n\t\t},\n\t};\n}\n\nexport function getNodeIdContext(compressor: IdCompressor): NodeIdContext & NodeIdNormalizer<OpSpaceNodeId> {\n\treturn {\n\t\tgenerateNodeId: (override?: string) => compressor.generateCompressedId(override) as NodeId,\n\t\tconvertToNodeId: (id: StableNodeId) => compressor.recompress(id) as NodeId,\n\t\ttryConvertToNodeId: (id: StableNodeId) => compressor.tryRecompress(id) as NodeId | undefined,\n\t\tconvertToStableNodeId: (id: NodeId) => compressor.decompress(id) as StableNodeId,\n\t\ttryConvertToStableNodeId: (id: NodeId) => compressor.tryDecompress(id) as StableNodeId,\n\t\tnormalizeToOpSpace: (id: NodeId) => compressor.normalizeToOpSpace(id) as OpSpaceNodeId,\n\t\tnormalizeToSessionSpace: (id: OpSpaceNodeId, sessionId: SessionId) =>\n\t\t\tcompressor.normalizeToSessionSpace(id, sessionId) as NodeId,\n\t\tlocalSessionId: compressor.localSessionId,\n\t};\n}\n\n/** Accepts either a node or a node's identifier, and returns the identifier */\nexport function getNodeId<TId extends number | string>(node: TId | NodeData<TId>): TId {\n\treturn (node as NodeData<TId>).identifier ?? (node as TId);\n}\n"]}
@@ -4,7 +4,7 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.comparePayloads = void 0;
7
+ exports.comparePayloads = comparePayloads;
8
8
  const internal_1 = require("@fluidframework/core-utils/internal");
9
9
  const runtime_utils_1 = require("@fluidframework/runtime-utils");
10
10
  /**
@@ -100,5 +100,4 @@ function comparePayloads(a, b) {
100
100
  }
101
101
  return true;
102
102
  }
103
- exports.comparePayloads = comparePayloads;
104
103
  //# sourceMappingURL=PayloadUtilities.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PayloadUtilities.js","sourceRoot":"","sources":["../src/PayloadUtilities.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,kEAAoE;AACpE,iEAAmF;AAInF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,SAAgB,eAAe,CAAC,CAAU,EAAE,CAAU;IACrD,iFAAiF;IACjF,sCAAsC;IACtC,0DAA0D;IAC1D,mFAAmF;IACnF,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,2HAA2H;IAC3H,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,qFAAqF;IACrF,uEAAuE;IACvE,0FAA0F;IAC1F,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACd,CAAC;IAED,wEAAwE;IACxE,IAAI,IAAA,6BAAa,EAAC,CAAC,CAAC,EAAE,CAAC;QACtB,IAAI,IAAA,6BAAa,EAAC,CAAC,CAAC,EAAE,CAAC;YACtB,OAAO,IAAA,mCAAmB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,IAAA,6BAAa,EAAC,CAAC,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACd,CAAC;IAED,0GAA0G;IAC1G,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE7B,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACd,CAAC;IAED,8EAA8E;IAC9E,IAAI,CAAC,YAAY,KAAK,KAAK,CAAC,YAAY,KAAK,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAC;IACd,CAAC;IAED,gIAAgI;IAChI,gDAAgD;IAChD,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,8BAA8B;IAC9B,+FAA+F;IAC/F,IAAI,CAAC,IAAA,wBAAa,EAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC;IACd,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAY,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,KAAK,GAAY,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAEnC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC;AApED,0CAoEC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { compareArrays } from '@fluidframework/core-utils/internal';\nimport { compareFluidHandles, isFluidHandle } from '@fluidframework/runtime-utils';\n\nimport { Payload } from './persisted-types/index.js';\n\n/**\n * @returns true if two `Payloads` are identical.\n * May return false for equivalent payloads encoded differently.\n *\n * Object field order and object identity are not considered significant, and are ignored by this function.\n * (This is because they may not be preserved through roundtrip).\n *\n * For other information which Fluid would lose on serialization round trip,\n * behavior is unspecified other than this this function is reflective (all payloads are equal to themselves)\n * and commutative (argument order does not matter).\n *\n * This means that any Payload is equal to itself and a deep clone of itself.\n *\n * Payloads might not be equal to a version of themselves that has been serialized then deserialized.\n * If they are serialized then deserialized again, the two deserialized objects will compare equal,\n * however the serialized strings may be unequal (due to field order for objects being unspecified).\n *\n * Fluid will cause lossy operations due to use of JSON.stringify().\n * This includes:\n * - Loss of object identity\n * - Loss of field order (may be ordered arbitrarily)\n * - -0 becomes +0\n * - NaN, Infinity, -Infinity all become null\n * - custom toJSON functions may cause arbitrary behavior\n * - functions become undefined or null\n * - non enumerable properties (including prototype) are lost\n * - more (this is not a complete list)\n *\n * Inputs must not contain cyclic references other than fields set to their immediate parent (for the JavaScript feature detection pattern).\n *\n * IFluidHandle instances (detected via JavaScript feature detection pattern) are only compared by absolutePath.\n *\n * TODO:#54095: Is there a better way to do this comparison?\n * @alpha\n */\nexport function comparePayloads(a: Payload, b: Payload): boolean {\n\t// === is not reflective because of how NaN is handled, so use Object.is instead.\n\t// This treats -0 and +0 as different.\n\t// Since -0 is not preserved in serialization round trips,\n\t// it can be handed in any way that is reflective and commutative, so this is fine.\n\tif (Object.is(a, b)) {\n\t\treturn true;\n\t}\n\n\t// Primitives which are equal would have early returned above, so now if the values are not both objects, they are unequal.\n\tif (typeof a !== 'object' || typeof b !== 'object') {\n\t\treturn false;\n\t}\n\n\t// null is of type object, and needs to be treated as distinct from the empty object.\n\t// Handling it early also avoids type errors trying to access its keys.\n\t// Rationale: 'undefined' payloads are reserved for future use (see 'SetValue' interface).\n\tif (a === null || b === null) {\n\t\treturn false;\n\t}\n\n\t// Special case IFluidHandles, comparing them only by their absolutePath\n\tif (isFluidHandle(a)) {\n\t\tif (isFluidHandle(b)) {\n\t\t\treturn compareFluidHandles(a, b);\n\t\t}\n\t\treturn false;\n\t}\n\tif (isFluidHandle(b)) {\n\t\treturn false;\n\t}\n\n\t// Fluid Serialization (like Json) only keeps enumerable properties, so we can ignore non-enumerable ones.\n\tconst aKeys = Object.keys(a);\n\tconst bKeys = Object.keys(b);\n\n\tif (aKeys.length !== bKeys.length) {\n\t\treturn false;\n\t}\n\n\t// make sure objects with numeric keys (or no keys) compare unequal to arrays.\n\tif (a instanceof Array !== b instanceof Array) {\n\t\treturn false;\n\t}\n\n\t// Fluid Serialization (like Json) orders object fields arbitrarily, so reordering fields is not considered considered a change.\n\t// Therefor the keys arrays must be sorted here.\n\tif (!(a instanceof Array)) {\n\t\taKeys.sort();\n\t\tbKeys.sort();\n\t}\n\n\t// First check keys are equal.\n\t// This will often early exit, and thus is worth doing as a separate pass than recursive check.\n\tif (!compareArrays(aKeys, bKeys)) {\n\t\treturn false;\n\t}\n\n\tfor (let i = 0; i < aKeys.length; i++) {\n\t\tconst aItem: Payload = a[aKeys[i]];\n\t\tconst bItem: Payload = b[bKeys[i]];\n\n\t\tif (!comparePayloads(aItem, bItem)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n"]}
1
+ {"version":3,"file":"PayloadUtilities.js","sourceRoot":"","sources":["../src/PayloadUtilities.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AA0CH,0CAoEC;AA5GD,kEAAoE;AACpE,iEAAmF;AAInF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,SAAgB,eAAe,CAAC,CAAU,EAAE,CAAU;IACrD,iFAAiF;IACjF,sCAAsC;IACtC,0DAA0D;IAC1D,mFAAmF;IACnF,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,2HAA2H;IAC3H,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,qFAAqF;IACrF,uEAAuE;IACvE,0FAA0F;IAC1F,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACd,CAAC;IAED,wEAAwE;IACxE,IAAI,IAAA,6BAAa,EAAC,CAAC,CAAC,EAAE,CAAC;QACtB,IAAI,IAAA,6BAAa,EAAC,CAAC,CAAC,EAAE,CAAC;YACtB,OAAO,IAAA,mCAAmB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,IAAA,6BAAa,EAAC,CAAC,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACd,CAAC;IAED,0GAA0G;IAC1G,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE7B,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACd,CAAC;IAED,8EAA8E;IAC9E,IAAI,CAAC,YAAY,KAAK,KAAK,CAAC,YAAY,KAAK,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAC;IACd,CAAC;IAED,gIAAgI;IAChI,gDAAgD;IAChD,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,8BAA8B;IAC9B,+FAA+F;IAC/F,IAAI,CAAC,IAAA,wBAAa,EAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC;IACd,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAY,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,KAAK,GAAY,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAEnC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { compareArrays } from '@fluidframework/core-utils/internal';\nimport { compareFluidHandles, isFluidHandle } from '@fluidframework/runtime-utils';\n\nimport { Payload } from './persisted-types/index.js';\n\n/**\n * @returns true if two `Payloads` are identical.\n * May return false for equivalent payloads encoded differently.\n *\n * Object field order and object identity are not considered significant, and are ignored by this function.\n * (This is because they may not be preserved through roundtrip).\n *\n * For other information which Fluid would lose on serialization round trip,\n * behavior is unspecified other than this this function is reflective (all payloads are equal to themselves)\n * and commutative (argument order does not matter).\n *\n * This means that any Payload is equal to itself and a deep clone of itself.\n *\n * Payloads might not be equal to a version of themselves that has been serialized then deserialized.\n * If they are serialized then deserialized again, the two deserialized objects will compare equal,\n * however the serialized strings may be unequal (due to field order for objects being unspecified).\n *\n * Fluid will cause lossy operations due to use of JSON.stringify().\n * This includes:\n * - Loss of object identity\n * - Loss of field order (may be ordered arbitrarily)\n * - -0 becomes +0\n * - NaN, Infinity, -Infinity all become null\n * - custom toJSON functions may cause arbitrary behavior\n * - functions become undefined or null\n * - non enumerable properties (including prototype) are lost\n * - more (this is not a complete list)\n *\n * Inputs must not contain cyclic references other than fields set to their immediate parent (for the JavaScript feature detection pattern).\n *\n * IFluidHandle instances (detected via JavaScript feature detection pattern) are only compared by absolutePath.\n *\n * TODO:#54095: Is there a better way to do this comparison?\n * @alpha\n */\nexport function comparePayloads(a: Payload, b: Payload): boolean {\n\t// === is not reflective because of how NaN is handled, so use Object.is instead.\n\t// This treats -0 and +0 as different.\n\t// Since -0 is not preserved in serialization round trips,\n\t// it can be handed in any way that is reflective and commutative, so this is fine.\n\tif (Object.is(a, b)) {\n\t\treturn true;\n\t}\n\n\t// Primitives which are equal would have early returned above, so now if the values are not both objects, they are unequal.\n\tif (typeof a !== 'object' || typeof b !== 'object') {\n\t\treturn false;\n\t}\n\n\t// null is of type object, and needs to be treated as distinct from the empty object.\n\t// Handling it early also avoids type errors trying to access its keys.\n\t// Rationale: 'undefined' payloads are reserved for future use (see 'SetValue' interface).\n\tif (a === null || b === null) {\n\t\treturn false;\n\t}\n\n\t// Special case IFluidHandles, comparing them only by their absolutePath\n\tif (isFluidHandle(a)) {\n\t\tif (isFluidHandle(b)) {\n\t\t\treturn compareFluidHandles(a, b);\n\t\t}\n\t\treturn false;\n\t}\n\tif (isFluidHandle(b)) {\n\t\treturn false;\n\t}\n\n\t// Fluid Serialization (like Json) only keeps enumerable properties, so we can ignore non-enumerable ones.\n\tconst aKeys = Object.keys(a);\n\tconst bKeys = Object.keys(b);\n\n\tif (aKeys.length !== bKeys.length) {\n\t\treturn false;\n\t}\n\n\t// make sure objects with numeric keys (or no keys) compare unequal to arrays.\n\tif (a instanceof Array !== b instanceof Array) {\n\t\treturn false;\n\t}\n\n\t// Fluid Serialization (like Json) orders object fields arbitrarily, so reordering fields is not considered considered a change.\n\t// Therefor the keys arrays must be sorted here.\n\tif (!(a instanceof Array)) {\n\t\taKeys.sort();\n\t\tbKeys.sort();\n\t}\n\n\t// First check keys are equal.\n\t// This will often early exit, and thus is worth doing as a separate pass than recursive check.\n\tif (!compareArrays(aKeys, bKeys)) {\n\t\treturn false;\n\t}\n\n\tfor (let i = 0; i < aKeys.length; i++) {\n\t\tconst aItem: Payload = a[aKeys[i]];\n\t\tconst bItem: Payload = b[bKeys[i]];\n\n\t\tif (!comparePayloads(aItem, bItem)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n"]}
@@ -26,6 +26,24 @@ const Common_js_1 = require("./Common.js");
26
26
  * `cacheRetainedValue`
27
27
  */
28
28
  class RevisionValueCache {
29
+ retentionWindowStart;
30
+ /**
31
+ * A cache of entries for revisions.
32
+ * This is sorted to allow efficient access to the nearest preceding entry (see getClosestEntry).
33
+ * Contains all cached values, regardless of why they are cached (retained, LRU or window).
34
+ */
35
+ sortedEntries = new sorted_btree_es6_1.BTree(undefined, Common_js_1.compareFiniteNumbers);
36
+ /**
37
+ * Cache of most recently used evictable entries.
38
+ * Subset of `sortedValues` eligible for eviction:
39
+ * All entries are also in `sortedValues`, and are removed from `sortedValues` when evicted from this cache.
40
+ * Evicts least recently used entries.
41
+ */
42
+ evictableRevisions;
43
+ /**
44
+ * The oldest revision that must be retained in memory.
45
+ */
46
+ retainedRevision;
29
47
  constructor(
30
48
  /**
31
49
  * Maximum capacity for evictable cache entries (those neither marked as retained nor within the retention window).
@@ -41,12 +59,6 @@ class RevisionValueCache {
41
59
  */
42
60
  retainedRevision) {
43
61
  this.retentionWindowStart = retentionWindowStart;
44
- /**
45
- * A cache of entries for revisions.
46
- * This is sorted to allow efficient access to the nearest preceding entry (see getClosestEntry).
47
- * Contains all cached values, regardless of why they are cached (retained, LRU or window).
48
- */
49
- this.sortedEntries = new sorted_btree_es6_1.BTree(undefined, Common_js_1.compareFiniteNumbers);
50
62
  (0, internal_1.assert)(retentionWindowStart >= 0, 0x62c /* retentionWindowStart must be initialized >= 0 */);
51
63
  this.evictableRevisions = new lru_cache_1.default({
52
64
  max: evictableSize,
@@ -1 +1 @@
1
- {"version":3,"file":"RevisionValueCache.js","sourceRoot":"","sources":["../src/RevisionValueCache.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 { assert } from '@fluidframework/core-utils/internal';\nimport { BTree } from '@tylerbu/sorted-btree-es6';\nimport LRU from 'lru-cache';\n\nimport { compareFiniteNumbers, fail } from './Common.js';\n\n/**\n * A revision corresponds to an index in an `EditLog`.\n *\n * It is associated with the output `RevisionView` of applying the edit at the index to the previous revision.\n * For example:\n *\n * - revision 0 corresponds to the initialRevision.\n *\n * - revision 1 corresponds to the output of editLog[0] applied to the initialRevision.\n * @alpha\n */\nexport type Revision = number;\n\n/**\n * A cache of `TValue`s corresponding to `Revision`s.\n *\n * A value is kept in cache if it meets any of the following criteria:\n *\n * - The revision is \\>= `retentionWindowStart`\n *\n * - The value has been used recently, meaning getClosestEntry or cacheValue was called with its revision. Note that\n * being returned when a large revision was passed to getClosestEntry does not count.\n *\n * - The value is `retained` meaning it was provided to to constructor in retainedEntries or passed to\n * `cacheRetainedValue`\n */\nexport class RevisionValueCache<TValue> {\n\t/**\n\t * A cache of entries for revisions.\n\t * This is sorted to allow efficient access to the nearest preceding entry (see getClosestEntry).\n\t * Contains all cached values, regardless of why they are cached (retained, LRU or window).\n\t */\n\tprivate readonly sortedEntries = new BTree<Revision, TValue>(undefined, compareFiniteNumbers);\n\n\t/**\n\t * Cache of most recently used evictable entries.\n\t * Subset of `sortedValues` eligible for eviction:\n\t * All entries are also in `sortedValues`, and are removed from `sortedValues` when evicted from this cache.\n\t * Evicts least recently used entries.\n\t */\n\tprivate readonly evictableRevisions: LRU<Revision, TValue>;\n\n\t/**\n\t * The oldest revision that must be retained in memory.\n\t */\n\tprivate retainedRevision?: Revision;\n\n\tpublic constructor(\n\t\t/**\n\t\t * Maximum capacity for evictable cache entries (those neither marked as retained nor within the retention window).\n\t\t */\n\t\tevictableSize: number,\n\t\t/**\n\t\t * The first revision within the retention window. All entries with revisions \\>= retentionWindowStart will be retained.\n\t\t * Must be \\>= 0.\n\t\t */\n\t\tprivate retentionWindowStart: Revision,\n\t\t/**\n\t\t * The oldest revision that must be retained in memory.\n\t\t */\n\t\tretainedRevision?: [Revision, TValue]\n\t) {\n\t\tassert(retentionWindowStart >= 0, 0x62c /* retentionWindowStart must be initialized >= 0 */);\n\t\tthis.evictableRevisions = new LRU({\n\t\t\tmax: evictableSize,\n\t\t\tnoDisposeOnSet: true,\n\t\t\tdispose: (revision) => {\n\t\t\t\tif (revision >= this.retentionWindowStart) {\n\t\t\t\t\tfail('Entries in retention window should never be evicted.');\n\t\t\t\t}\n\t\t\t\tif (this.retainedRevision === revision) {\n\t\t\t\t\tfail('Retained entries should not be evicted');\n\t\t\t\t}\n\t\t\t\tthis.sortedEntries.delete(revision);\n\t\t\t},\n\t\t});\n\n\t\tif (retainedRevision !== undefined) {\n\t\t\tthis.cacheRetainedValue(retainedRevision[0], retainedRevision[1]);\n\t\t}\n\t}\n\n\t/**\n\t * @returns if the supplied revision is within the retention window.\n\t */\n\tpublic isWithinRetentionWindow(revision: Revision): boolean {\n\t\treturn revision >= this.retentionWindowStart;\n\t}\n\n\t/**\n\t * Sets the new retention window.\n\t * @param newRetentionWindowStart - defines the trailing edge (inclusive) of the new retention window.\n\t */\n\tpublic updateRetentionWindow(newRetentionWindowStart: Revision): void {\n\t\tif (newRetentionWindowStart < this.retentionWindowStart) {\n\t\t\tfail('retention window boundary must not move backwards');\n\t\t}\n\t\tconst prevRetentionWindowStart = this.retentionWindowStart;\n\t\tthis.retentionWindowStart = newRetentionWindowStart;\n\t\tconst oldWindowEntries: [Revision, TValue][] = [];\n\t\tthis.sortedEntries.forRange(\n\t\t\tprevRetentionWindowStart,\n\t\t\tthis.retentionWindowStart,\n\t\t\tfalse,\n\t\t\t(windowRevision, windowEntry) => {\n\t\t\t\tif (this.retainedRevision !== windowRevision) {\n\t\t\t\t\t// Adding to the LRU can cause eviction which in turn mutates the b-tree we are enumerating. Thus, store list of\n\t\t\t\t\t// old window entries separately.\n\t\t\t\t\toldWindowEntries.push([windowRevision, windowEntry]);\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t\toldWindowEntries.forEach(([revision, value]) => {\n\t\t\tthis.evictableRevisions.set(revision, value);\n\t\t});\n\t}\n\n\t/**\n\t * @returns a [cachedRevision, value] where cachedRevision \\<= requestedRevision, or undefined if no such revision\n\t * is cached.\n\t */\n\tpublic getClosestEntry(requestedRevision: Revision): [revision: Revision, value: TValue] | undefined {\n\t\tconst fromLRU = this.evictableRevisions.get(requestedRevision);\n\t\tif (fromLRU !== undefined) {\n\t\t\treturn [requestedRevision, fromLRU];\n\t\t}\n\t\treturn this.sortedEntries.getPairOrNextLower(requestedRevision) ?? undefined;\n\t}\n\n\t/**\n\t * Caches the supplied value and guarantees it will never be evicted.\n\t * This will make the previously retained value evictable.\n\t */\n\tpublic cacheRetainedValue(revision: Revision, value: TValue): void {\n\t\tif (this.retainedRevision !== undefined) {\n\t\t\tthis.sortedEntries.delete(this.retainedRevision);\n\t\t}\n\t\tthis.retainedRevision = revision;\n\t\tthis.sortedEntries.set(revision, value);\n\t}\n\n\t/**\n\t * Caches the supplied value.\n\t * The cached value is subject to eviction unless it is within the retention window or was previously added\n\t * via `cacheRetainedValue`.\n\t * Note that if a non-retained entry starts out within the retention window and passes outside of it due to a call to\n\t * updateRetentionWindow it is then subject to eviction.\n\t */\n\tpublic cacheValue(revision: Revision, value: TValue): void {\n\t\tif (this.retainedRevision === revision) {\n\t\t\treturn;\n\t\t}\n\t\tthis.sortedEntries.set(revision, value);\n\t\tif (revision < this.retentionWindowStart) {\n\t\t\tthis.evictableRevisions.set(revision, value);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"RevisionValueCache.js","sourceRoot":"","sources":["../src/RevisionValueCache.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 { assert } from '@fluidframework/core-utils/internal';\nimport { BTree } from '@tylerbu/sorted-btree-es6';\nimport LRU from 'lru-cache';\n\nimport { compareFiniteNumbers, fail } from './Common.js';\n\n/**\n * A revision corresponds to an index in an `EditLog`.\n *\n * It is associated with the output `RevisionView` of applying the edit at the index to the previous revision.\n * For example:\n *\n * - revision 0 corresponds to the initialRevision.\n *\n * - revision 1 corresponds to the output of editLog[0] applied to the initialRevision.\n * @alpha\n */\nexport type Revision = number;\n\n/**\n * A cache of `TValue`s corresponding to `Revision`s.\n *\n * A value is kept in cache if it meets any of the following criteria:\n *\n * - The revision is \\>= `retentionWindowStart`\n *\n * - The value has been used recently, meaning getClosestEntry or cacheValue was called with its revision. Note that\n * being returned when a large revision was passed to getClosestEntry does not count.\n *\n * - The value is `retained` meaning it was provided to to constructor in retainedEntries or passed to\n * `cacheRetainedValue`\n */\nexport class RevisionValueCache<TValue> {\n\t/**\n\t * A cache of entries for revisions.\n\t * This is sorted to allow efficient access to the nearest preceding entry (see getClosestEntry).\n\t * Contains all cached values, regardless of why they are cached (retained, LRU or window).\n\t */\n\tprivate readonly sortedEntries = new BTree<Revision, TValue>(undefined, compareFiniteNumbers);\n\n\t/**\n\t * Cache of most recently used evictable entries.\n\t * Subset of `sortedValues` eligible for eviction:\n\t * All entries are also in `sortedValues`, and are removed from `sortedValues` when evicted from this cache.\n\t * Evicts least recently used entries.\n\t */\n\tprivate readonly evictableRevisions: LRU<Revision, TValue>;\n\n\t/**\n\t * The oldest revision that must be retained in memory.\n\t */\n\tprivate retainedRevision?: Revision;\n\n\tpublic constructor(\n\t\t/**\n\t\t * Maximum capacity for evictable cache entries (those neither marked as retained nor within the retention window).\n\t\t */\n\t\tevictableSize: number,\n\t\t/**\n\t\t * The first revision within the retention window. All entries with revisions \\>= retentionWindowStart will be retained.\n\t\t * Must be \\>= 0.\n\t\t */\n\t\tprivate retentionWindowStart: Revision,\n\t\t/**\n\t\t * The oldest revision that must be retained in memory.\n\t\t */\n\t\tretainedRevision?: [Revision, TValue]\n\t) {\n\t\tassert(retentionWindowStart >= 0, 0x62c /* retentionWindowStart must be initialized >= 0 */);\n\t\tthis.evictableRevisions = new LRU({\n\t\t\tmax: evictableSize,\n\t\t\tnoDisposeOnSet: true,\n\t\t\tdispose: (revision) => {\n\t\t\t\tif (revision >= this.retentionWindowStart) {\n\t\t\t\t\tfail('Entries in retention window should never be evicted.');\n\t\t\t\t}\n\t\t\t\tif (this.retainedRevision === revision) {\n\t\t\t\t\tfail('Retained entries should not be evicted');\n\t\t\t\t}\n\t\t\t\tthis.sortedEntries.delete(revision);\n\t\t\t},\n\t\t});\n\n\t\tif (retainedRevision !== undefined) {\n\t\t\tthis.cacheRetainedValue(retainedRevision[0], retainedRevision[1]);\n\t\t}\n\t}\n\n\t/**\n\t * @returns if the supplied revision is within the retention window.\n\t */\n\tpublic isWithinRetentionWindow(revision: Revision): boolean {\n\t\treturn revision >= this.retentionWindowStart;\n\t}\n\n\t/**\n\t * Sets the new retention window.\n\t * @param newRetentionWindowStart - defines the trailing edge (inclusive) of the new retention window.\n\t */\n\tpublic updateRetentionWindow(newRetentionWindowStart: Revision): void {\n\t\tif (newRetentionWindowStart < this.retentionWindowStart) {\n\t\t\tfail('retention window boundary must not move backwards');\n\t\t}\n\t\tconst prevRetentionWindowStart = this.retentionWindowStart;\n\t\tthis.retentionWindowStart = newRetentionWindowStart;\n\t\tconst oldWindowEntries: [Revision, TValue][] = [];\n\t\tthis.sortedEntries.forRange(\n\t\t\tprevRetentionWindowStart,\n\t\t\tthis.retentionWindowStart,\n\t\t\tfalse,\n\t\t\t(windowRevision, windowEntry) => {\n\t\t\t\tif (this.retainedRevision !== windowRevision) {\n\t\t\t\t\t// Adding to the LRU can cause eviction which in turn mutates the b-tree we are enumerating. Thus, store list of\n\t\t\t\t\t// old window entries separately.\n\t\t\t\t\toldWindowEntries.push([windowRevision, windowEntry]);\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t\toldWindowEntries.forEach(([revision, value]) => {\n\t\t\tthis.evictableRevisions.set(revision, value);\n\t\t});\n\t}\n\n\t/**\n\t * @returns a [cachedRevision, value] where cachedRevision \\<= requestedRevision, or undefined if no such revision\n\t * is cached.\n\t */\n\tpublic getClosestEntry(requestedRevision: Revision): [revision: Revision, value: TValue] | undefined {\n\t\tconst fromLRU = this.evictableRevisions.get(requestedRevision);\n\t\tif (fromLRU !== undefined) {\n\t\t\treturn [requestedRevision, fromLRU];\n\t\t}\n\t\treturn this.sortedEntries.getPairOrNextLower(requestedRevision) ?? undefined;\n\t}\n\n\t/**\n\t * Caches the supplied value and guarantees it will never be evicted.\n\t * This will make the previously retained value evictable.\n\t */\n\tpublic cacheRetainedValue(revision: Revision, value: TValue): void {\n\t\tif (this.retainedRevision !== undefined) {\n\t\t\tthis.sortedEntries.delete(this.retainedRevision);\n\t\t}\n\t\tthis.retainedRevision = revision;\n\t\tthis.sortedEntries.set(revision, value);\n\t}\n\n\t/**\n\t * Caches the supplied value.\n\t * The cached value is subject to eviction unless it is within the retention window or was previously added\n\t * via `cacheRetainedValue`.\n\t * Note that if a non-retained entry starts out within the retention window and passes outside of it due to a call to\n\t * updateRetentionWindow it is then subject to eviction.\n\t */\n\tpublic cacheValue(revision: Revision, value: TValue): void {\n\t\tif (this.retainedRevision === revision) {\n\t\t\treturn;\n\t\t}\n\t\tthis.sortedEntries.set(revision, value);\n\t\tif (revision < this.retentionWindowStart) {\n\t\t\tthis.evictableRevisions.set(revision, value);\n\t\t}\n\t}\n}\n"]}
@@ -4,7 +4,9 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.iterateChildren = exports.convertTreeNodesToViewNodes = exports.TransactionView = exports.RevisionView = void 0;
7
+ exports.TransactionView = exports.RevisionView = void 0;
8
+ exports.convertTreeNodesToViewNodes = convertTreeNodesToViewNodes;
9
+ exports.iterateChildren = iterateChildren;
8
10
  const Common_js_1 = require("./Common.js");
9
11
  const Forest_js_1 = require("./Forest.js");
10
12
  const TreeView_js_1 = require("./TreeView.js");
@@ -154,7 +156,6 @@ function convertTreeNodesToViewNodes(root, convert) {
154
156
  }
155
157
  return resultNodes;
156
158
  }
157
- exports.convertTreeNodesToViewNodes = convertTreeNodesToViewNodes;
158
159
  /**
159
160
  * Returns an iterable of the supplied node's traits in a stable order.
160
161
  */
@@ -174,7 +175,6 @@ function* iterateChildren(hasTraits) {
174
175
  }
175
176
  }
176
177
  }
177
- exports.iterateChildren = iterateChildren;
178
178
  function isTreeNodeSequence(sequence) {
179
179
  return Array.isArray(sequence);
180
180
  }