@fluidframework/id-compressor 2.0.0-dev-rc.1.0.0.228517 → 2.0.0-dev-rc.2.0.0.245554

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 (264) hide show
  1. package/{.eslintrc.js → .eslintrc.cjs} +5 -5
  2. package/{.mocharc.js → .mocharc.cjs} +1 -1
  3. package/CHANGELOG.md +8 -0
  4. package/{api-extractor-esm.json → api-extractor-cjs.json} +5 -1
  5. package/api-extractor-lint.json +1 -1
  6. package/api-extractor.json +1 -1
  7. package/api-report/id-compressor.api.md +5 -42
  8. package/dist/finalSpace.d.ts +2 -2
  9. package/dist/finalSpace.d.ts.map +1 -1
  10. package/dist/finalSpace.js +4 -4
  11. package/dist/finalSpace.js.map +1 -1
  12. package/dist/id-compressor-alpha.d.ts +117 -112
  13. package/dist/id-compressor-beta.d.ts +84 -77
  14. package/dist/id-compressor-public.d.ts +84 -77
  15. package/dist/id-compressor-untrimmed.d.ts +117 -112
  16. package/dist/idCompressor.d.ts +15 -19
  17. package/dist/idCompressor.d.ts.map +1 -1
  18. package/dist/idCompressor.js +107 -94
  19. package/dist/idCompressor.js.map +1 -1
  20. package/dist/identifiers.d.ts +1 -1
  21. package/dist/identifiers.d.ts.map +1 -1
  22. package/dist/identifiers.js.map +1 -1
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +9 -10
  26. package/dist/index.js.map +1 -1
  27. package/dist/package.json +3 -0
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/packageVersion.js +1 -1
  30. package/dist/packageVersion.js.map +1 -1
  31. package/dist/persistanceUtilities.d.ts +1 -1
  32. package/dist/persistanceUtilities.d.ts.map +1 -1
  33. package/dist/persistanceUtilities.js.map +1 -1
  34. package/dist/sessionSpaceNormalizer.d.ts +2 -2
  35. package/dist/sessionSpaceNormalizer.d.ts.map +1 -1
  36. package/dist/sessionSpaceNormalizer.js +4 -4
  37. package/dist/sessionSpaceNormalizer.js.map +1 -1
  38. package/dist/sessions.d.ts +2 -2
  39. package/dist/sessions.d.ts.map +1 -1
  40. package/dist/sessions.js +12 -12
  41. package/dist/sessions.js.map +1 -1
  42. package/dist/test/appendOnlySortedMap.perf.spec.d.ts +6 -0
  43. package/dist/test/appendOnlySortedMap.perf.spec.d.ts.map +1 -0
  44. package/dist/test/appendOnlySortedMap.perf.spec.js.map +1 -0
  45. package/dist/test/appendOnlySortedMap.spec.d.ts +6 -0
  46. package/dist/test/appendOnlySortedMap.spec.d.ts.map +1 -0
  47. package/dist/test/appendOnlySortedMap.spec.js.map +1 -0
  48. package/dist/test/idCompressor.perf.spec.d.ts +6 -0
  49. package/dist/test/idCompressor.perf.spec.d.ts.map +1 -0
  50. package/dist/test/idCompressor.perf.spec.js.map +1 -0
  51. package/dist/test/idCompressor.spec.d.ts +6 -0
  52. package/dist/test/idCompressor.spec.d.ts.map +1 -0
  53. package/dist/test/idCompressor.spec.js.map +1 -0
  54. package/dist/test/idCompressorTestUtilities.d.ts +245 -0
  55. package/dist/test/idCompressorTestUtilities.d.ts.map +1 -0
  56. package/dist/test/idCompressorTestUtilities.js +589 -0
  57. package/dist/test/idCompressorTestUtilities.js.map +1 -0
  58. package/dist/test/index.d.ts +9 -0
  59. package/dist/test/index.d.ts.map +1 -0
  60. package/dist/test/index.js +13 -0
  61. package/dist/test/index.js.map +1 -0
  62. package/dist/test/numericUuid.spec.d.ts +6 -0
  63. package/dist/test/numericUuid.spec.d.ts.map +1 -0
  64. package/dist/test/numericUuid.spec.js.map +1 -0
  65. package/dist/test/sessionSpaceNormalizer.spec.d.ts +6 -0
  66. package/dist/test/sessionSpaceNormalizer.spec.d.ts.map +1 -0
  67. package/dist/test/sessionSpaceNormalizer.spec.js.map +1 -0
  68. package/dist/test/snapshots/dirname.cjs +16 -0
  69. package/dist/test/snapshots/dirname.cjs.map +1 -0
  70. package/dist/test/snapshots/dirname.d.cts +6 -0
  71. package/dist/test/snapshots/dirname.d.cts.map +1 -0
  72. package/{lib/types/idCompressor.mjs → dist/test/snapshots/summary.spec.d.ts} +1 -1
  73. package/dist/test/snapshots/summary.spec.d.ts.map +1 -0
  74. package/dist/test/snapshots/summary.spec.js.map +1 -0
  75. package/dist/test/testCommon.d.ts +90 -0
  76. package/dist/test/testCommon.d.ts.map +1 -0
  77. package/dist/test/testCommon.js +90 -0
  78. package/dist/test/testCommon.js.map +1 -0
  79. package/dist/tsdoc-metadata.json +1 -1
  80. package/dist/types/idCompressor.d.ts +134 -67
  81. package/dist/types/idCompressor.d.ts.map +1 -1
  82. package/dist/types/idCompressor.js.map +1 -1
  83. package/dist/types/index.d.ts +3 -3
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/dist/types/index.js.map +1 -1
  86. package/dist/types/persisted-types/0.0.1.d.ts +1 -1
  87. package/dist/types/persisted-types/0.0.1.d.ts.map +1 -1
  88. package/dist/types/persisted-types/0.0.1.js.map +1 -1
  89. package/dist/types/persisted-types/index.d.ts +1 -1
  90. package/dist/types/persisted-types/index.d.ts.map +1 -1
  91. package/dist/types/persisted-types/index.js.map +1 -1
  92. package/dist/utilities.d.ts +2 -2
  93. package/dist/utilities.d.ts.map +1 -1
  94. package/dist/utilities.js.map +1 -1
  95. package/lib/{appendOnlySortedMap.d.mts → appendOnlySortedMap.d.ts} +1 -1
  96. package/lib/appendOnlySortedMap.d.ts.map +1 -0
  97. package/lib/{appendOnlySortedMap.mjs → appendOnlySortedMap.js} +3 -1
  98. package/lib/appendOnlySortedMap.js.map +1 -0
  99. package/lib/{finalSpace.d.mts → finalSpace.d.ts} +3 -3
  100. package/lib/finalSpace.d.ts.map +1 -0
  101. package/lib/{finalSpace.mjs → finalSpace.js} +2 -2
  102. package/lib/finalSpace.js.map +1 -0
  103. package/lib/{id-compressor-alpha.d.mts → id-compressor-alpha.d.ts} +117 -112
  104. package/lib/id-compressor-beta.d.ts +179 -0
  105. package/lib/id-compressor-public.d.ts +179 -0
  106. package/lib/{id-compressor-untrimmed.d.mts → id-compressor-untrimmed.d.ts} +117 -112
  107. package/lib/{idCompressor.d.mts → idCompressor.d.ts} +16 -20
  108. package/lib/idCompressor.d.ts.map +1 -0
  109. package/lib/{idCompressor.mjs → idCompressor.js} +54 -41
  110. package/lib/idCompressor.js.map +1 -0
  111. package/lib/{identifiers.d.mts → identifiers.d.ts} +2 -2
  112. package/lib/identifiers.d.ts.map +1 -0
  113. package/lib/{identifiers.mjs → identifiers.js} +1 -1
  114. package/lib/identifiers.js.map +1 -0
  115. package/lib/{index.d.mts → index.d.ts} +7 -4
  116. package/lib/index.d.ts.map +1 -0
  117. package/lib/index.js +10 -0
  118. package/lib/index.js.map +1 -0
  119. package/lib/{packageVersion.d.mts → packageVersion.d.ts} +2 -2
  120. package/lib/packageVersion.d.ts.map +1 -0
  121. package/lib/{packageVersion.mjs → packageVersion.js} +2 -2
  122. package/lib/packageVersion.js.map +1 -0
  123. package/lib/{persistanceUtilities.d.mts → persistanceUtilities.d.ts} +2 -2
  124. package/lib/persistanceUtilities.d.ts.map +1 -0
  125. package/lib/{persistanceUtilities.mjs → persistanceUtilities.js} +1 -1
  126. package/lib/persistanceUtilities.js.map +1 -0
  127. package/lib/{sessionSpaceNormalizer.d.mts → sessionSpaceNormalizer.d.ts} +3 -3
  128. package/lib/sessionSpaceNormalizer.d.ts.map +1 -0
  129. package/lib/{sessionSpaceNormalizer.mjs → sessionSpaceNormalizer.js} +3 -3
  130. package/lib/sessionSpaceNormalizer.js.map +1 -0
  131. package/lib/{sessions.d.mts → sessions.d.ts} +3 -3
  132. package/lib/sessions.d.ts.map +1 -0
  133. package/lib/{sessions.mjs → sessions.js} +2 -2
  134. package/lib/sessions.js.map +1 -0
  135. package/lib/test/appendOnlySortedMap.perf.spec.d.ts +6 -0
  136. package/lib/test/appendOnlySortedMap.perf.spec.d.ts.map +1 -0
  137. package/lib/test/appendOnlySortedMap.perf.spec.js +49 -0
  138. package/lib/test/appendOnlySortedMap.perf.spec.js.map +1 -0
  139. package/lib/test/appendOnlySortedMap.spec.d.ts +6 -0
  140. package/lib/test/appendOnlySortedMap.spec.d.ts.map +1 -0
  141. package/lib/test/appendOnlySortedMap.spec.js +166 -0
  142. package/lib/test/appendOnlySortedMap.spec.js.map +1 -0
  143. package/lib/test/idCompressor.perf.spec.d.ts +6 -0
  144. package/lib/test/idCompressor.perf.spec.d.ts.map +1 -0
  145. package/lib/test/idCompressor.perf.spec.js +281 -0
  146. package/lib/test/idCompressor.perf.spec.js.map +1 -0
  147. package/lib/test/idCompressor.spec.d.ts +6 -0
  148. package/lib/test/idCompressor.spec.d.ts.map +1 -0
  149. package/lib/test/idCompressor.spec.js +953 -0
  150. package/lib/test/idCompressor.spec.js.map +1 -0
  151. package/lib/test/idCompressorTestUtilities.d.ts +245 -0
  152. package/lib/test/idCompressorTestUtilities.d.ts.map +1 -0
  153. package/lib/test/idCompressorTestUtilities.js +576 -0
  154. package/lib/test/idCompressorTestUtilities.js.map +1 -0
  155. package/lib/test/index.d.ts +9 -0
  156. package/lib/test/index.d.ts.map +1 -0
  157. package/lib/test/index.js +9 -0
  158. package/lib/test/index.js.map +1 -0
  159. package/lib/test/numericUuid.spec.d.ts +6 -0
  160. package/lib/test/numericUuid.spec.d.ts.map +1 -0
  161. package/lib/test/numericUuid.spec.js +74 -0
  162. package/lib/test/numericUuid.spec.js.map +1 -0
  163. package/lib/test/sessionSpaceNormalizer.spec.d.ts +6 -0
  164. package/lib/test/sessionSpaceNormalizer.spec.d.ts.map +1 -0
  165. package/lib/test/sessionSpaceNormalizer.spec.js +43 -0
  166. package/lib/test/sessionSpaceNormalizer.spec.js.map +1 -0
  167. package/lib/test/snapshots/dirname.cjs +16 -0
  168. package/lib/test/snapshots/dirname.cjs.map +1 -0
  169. package/lib/test/snapshots/dirname.d.cts +6 -0
  170. package/lib/test/snapshots/dirname.d.cts.map +1 -0
  171. package/lib/test/snapshots/summary.spec.d.ts +6 -0
  172. package/lib/test/snapshots/summary.spec.d.ts.map +1 -0
  173. package/lib/test/snapshots/summary.spec.js +127 -0
  174. package/lib/test/snapshots/summary.spec.js.map +1 -0
  175. package/lib/test/testCommon.d.ts +90 -0
  176. package/lib/test/testCommon.d.ts.map +1 -0
  177. package/lib/test/testCommon.js +80 -0
  178. package/lib/test/testCommon.js.map +1 -0
  179. package/lib/types/{idCompressor.d.mts → idCompressor.d.ts} +135 -68
  180. package/lib/types/idCompressor.d.ts.map +1 -0
  181. package/lib/types/{identifiers.mjs → idCompressor.js} +1 -1
  182. package/lib/types/idCompressor.js.map +1 -0
  183. package/lib/types/{identifiers.d.mts → identifiers.d.ts} +1 -1
  184. package/lib/types/identifiers.d.ts.map +1 -0
  185. package/lib/types/identifiers.js +6 -0
  186. package/lib/types/identifiers.js.map +1 -0
  187. package/lib/types/{index.d.mts → index.d.ts} +4 -4
  188. package/lib/types/index.d.ts.map +1 -0
  189. package/lib/types/{persisted-types/0.0.1.mjs → index.js} +1 -1
  190. package/lib/types/index.js.map +1 -0
  191. package/lib/types/persisted-types/{0.0.1.d.mts → 0.0.1.d.ts} +2 -2
  192. package/lib/types/persisted-types/0.0.1.d.ts.map +1 -0
  193. package/lib/types/persisted-types/{index.mjs → 0.0.1.js} +1 -1
  194. package/lib/types/persisted-types/0.0.1.js.map +1 -0
  195. package/lib/types/persisted-types/{index.d.mts → index.d.ts} +2 -2
  196. package/lib/types/persisted-types/index.d.ts.map +1 -0
  197. package/lib/types/{index.mjs → persisted-types/index.js} +1 -1
  198. package/lib/types/persisted-types/index.js.map +1 -0
  199. package/lib/{utilities.d.mts → utilities.d.ts} +3 -3
  200. package/lib/utilities.d.ts.map +1 -0
  201. package/lib/{utilities.mjs → utilities.js} +2 -1
  202. package/lib/utilities.js.map +1 -0
  203. package/package.json +88 -21
  204. package/src/finalSpace.ts +2 -2
  205. package/src/idCompressor.ts +75 -59
  206. package/src/identifiers.ts +1 -1
  207. package/src/index.ts +3 -3
  208. package/src/packageVersion.ts +1 -1
  209. package/src/persistanceUtilities.ts +1 -1
  210. package/src/sessionSpaceNormalizer.ts +3 -3
  211. package/src/sessions.ts +3 -3
  212. package/src/test/idCompressorTestUtilities.ts +921 -0
  213. package/src/test/index.ts +10 -0
  214. package/src/test/snapshots/dirname.cts +13 -0
  215. package/src/test/snapshots/files/compressor-with-finalized-range-from-one-client +1 -0
  216. package/src/test/snapshots/files/compressor-with-finalized-range-from-one-client-client2 +1 -0
  217. package/src/test/snapshots/files/compressors-with-finalized-ranges-from-two-clients +1 -0
  218. package/src/test/snapshots/files/compressors-with-finalized-ranges-from-two-clients-client2 +1 -0
  219. package/src/test/snapshots/files/empty-compressor-summary +1 -0
  220. package/src/test/snapshots/files/expansion-semantics +1 -0
  221. package/src/test/snapshots/files/expansion-semantics-client2 +1 -0
  222. package/src/test/testCommon.ts +143 -0
  223. package/src/test/tsconfig.cjs.json +12 -0
  224. package/src/test/tsconfig.json +20 -0
  225. package/src/types/idCompressor.ts +147 -72
  226. package/src/types/index.ts +8 -3
  227. package/src/types/persisted-types/0.0.1.ts +1 -1
  228. package/src/types/persisted-types/index.ts +1 -1
  229. package/src/utilities.ts +2 -2
  230. package/tsconfig.cjs.json +7 -0
  231. package/tsconfig.json +2 -5
  232. package/lib/appendOnlySortedMap.d.mts.map +0 -1
  233. package/lib/appendOnlySortedMap.mjs.map +0 -1
  234. package/lib/finalSpace.d.mts.map +0 -1
  235. package/lib/finalSpace.mjs.map +0 -1
  236. package/lib/id-compressor-beta.d.mts +0 -172
  237. package/lib/id-compressor-public.d.mts +0 -172
  238. package/lib/idCompressor.d.mts.map +0 -1
  239. package/lib/idCompressor.mjs.map +0 -1
  240. package/lib/identifiers.d.mts.map +0 -1
  241. package/lib/identifiers.mjs.map +0 -1
  242. package/lib/index.d.mts.map +0 -1
  243. package/lib/index.mjs +0 -7
  244. package/lib/index.mjs.map +0 -1
  245. package/lib/packageVersion.d.mts.map +0 -1
  246. package/lib/packageVersion.mjs.map +0 -1
  247. package/lib/persistanceUtilities.d.mts.map +0 -1
  248. package/lib/persistanceUtilities.mjs.map +0 -1
  249. package/lib/sessionSpaceNormalizer.d.mts.map +0 -1
  250. package/lib/sessionSpaceNormalizer.mjs.map +0 -1
  251. package/lib/sessions.d.mts.map +0 -1
  252. package/lib/sessions.mjs.map +0 -1
  253. package/lib/types/idCompressor.d.mts.map +0 -1
  254. package/lib/types/idCompressor.mjs.map +0 -1
  255. package/lib/types/identifiers.d.mts.map +0 -1
  256. package/lib/types/identifiers.mjs.map +0 -1
  257. package/lib/types/index.d.mts.map +0 -1
  258. package/lib/types/index.mjs.map +0 -1
  259. package/lib/types/persisted-types/0.0.1.d.mts.map +0 -1
  260. package/lib/types/persisted-types/0.0.1.mjs.map +0 -1
  261. package/lib/types/persisted-types/index.d.mts.map +0 -1
  262. package/lib/types/persisted-types/index.mjs.map +0 -1
  263. package/lib/utilities.d.mts.map +0 -1
  264. package/lib/utilities.mjs.map +0 -1
@@ -0,0 +1,576 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "assert";
6
+ import { createWeightedGenerator, interleave, makeRandom, performFuzzActions as performFuzzActionsBase, repeat, take, } from "@fluid-private/stochastic-test-utils";
7
+ import { createIdCompressor, } from "..//index.js";
8
+ import { IdCompressor } from "../idCompressor.js";
9
+ import { assertIsSessionId, createSessionId } from "../utilities.js";
10
+ import { getOrCreate, incrementStableId, isFinalId, isLocalId, fail, } from "./testCommon.js";
11
+ /** Identifies a compressor in a network */
12
+ export var Client;
13
+ (function (Client) {
14
+ Client["Client1"] = "Client1";
15
+ Client["Client2"] = "Client2";
16
+ Client["Client3"] = "Client3";
17
+ })(Client || (Client = {}));
18
+ /** Identifies categories of compressors */
19
+ export var MetaClient;
20
+ (function (MetaClient) {
21
+ MetaClient["All"] = "All";
22
+ })(MetaClient || (MetaClient = {}));
23
+ /** Identifies a compressor inside the network but outside the three specially tracked clients. */
24
+ export var OutsideClient;
25
+ (function (OutsideClient) {
26
+ OutsideClient["Remote"] = "Remote";
27
+ })(OutsideClient || (OutsideClient = {}));
28
+ export const OriginatingClient = { ...Client, ...OutsideClient };
29
+ export const DestinationClient = { ...Client, ...MetaClient };
30
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class
31
+ export class CompressorFactory {
32
+ /**
33
+ * Creates a new compressor with the supplied cluster capacity.
34
+ */
35
+ static createCompressor(client, clusterCapacity = 5, logger) {
36
+ return CompressorFactory.createCompressorWithSession(sessionIds.get(client), clusterCapacity, logger);
37
+ }
38
+ /**
39
+ * Creates a new compressor with the supplied cluster capacity.
40
+ */
41
+ static createCompressorWithSession(sessionId, clusterCapacity = 5, logger) {
42
+ const compressor = createIdCompressor(sessionId, logger);
43
+ // eslint-disable-next-line @typescript-eslint/dot-notation
44
+ compressor["nextRequestedClusterSize"] = clusterCapacity;
45
+ return compressor;
46
+ }
47
+ }
48
+ /**
49
+ * Utility for building a huge compressor.
50
+ * Build via the compressor factory.
51
+ */
52
+ export function buildHugeCompressor(numSessions = 10000, capacity = 10, numClustersPerSession = 3) {
53
+ const compressor = CompressorFactory.createCompressorWithSession(createSessionId(), capacity);
54
+ const sessions = [];
55
+ for (let i = 0; i < numSessions; i++) {
56
+ sessions.push(createSessionId());
57
+ }
58
+ for (let i = 0; i < numSessions * numClustersPerSession; i++) {
59
+ const sessionId = sessions[i % numSessions];
60
+ if (Math.random() > 0.1) {
61
+ for (let j = 0; j < Math.round(capacity / 2); j++) {
62
+ compressor.generateCompressedId();
63
+ }
64
+ compressor.finalizeCreationRange(compressor.takeNextCreationRange());
65
+ }
66
+ compressor.finalizeCreationRange({
67
+ sessionId,
68
+ ids: {
69
+ firstGenCount: Math.floor(i / numSessions) * capacity + 1,
70
+ count: capacity,
71
+ requestedClusterSize: capacity,
72
+ },
73
+ });
74
+ }
75
+ return compressor;
76
+ }
77
+ function makeSessionIds() {
78
+ const stableIds = new Map();
79
+ const clients = Object.values(Client);
80
+ for (let i = 0; i < clients.length; i++) {
81
+ // Place session uuids roughly in the middle of uuid space to increase odds of encountering interesting
82
+ // orderings in sorted collections
83
+ const sessionId = assertIsSessionId(`88888888-8888-4888-b${i}88-888888888888`);
84
+ stableIds.set(clients[i], sessionId);
85
+ }
86
+ return stableIds;
87
+ }
88
+ /**
89
+ * An array of session ID strings corresponding to all non-local `Client` entries.
90
+ */
91
+ export const sessionIds = makeSessionIds();
92
+ /**
93
+ * Simulates a network of ID compressors.
94
+ * Not suitable for performance testing.
95
+ */
96
+ export class IdCompressorTestNetwork {
97
+ constructor(initialClusterSize = 5) {
98
+ this.initialClusterSize = initialClusterSize;
99
+ /** The log of operations seen by the server so far. Append-only. */
100
+ this.serverOperations = [];
101
+ const compressors = new Map();
102
+ const clientProgress = new Map();
103
+ const clientIds = new Map();
104
+ const clientSequencedIds = new Map();
105
+ for (const client of Object.values(Client)) {
106
+ const compressor = CompressorFactory.createCompressor(client, initialClusterSize);
107
+ compressors.set(client, compressor);
108
+ clientProgress.set(client, 0);
109
+ clientIds.set(client, []);
110
+ clientSequencedIds.set(client, []);
111
+ }
112
+ this.compressors = compressors;
113
+ this.clientProgress = clientProgress;
114
+ this.idLogs = clientIds;
115
+ this.sequencedIdLogs = clientSequencedIds;
116
+ }
117
+ /**
118
+ * Returns the number of undelivered operations for the given client that are in flight in the network.
119
+ */
120
+ getPendingOperations(destination) {
121
+ return this.serverOperations.length - this.clientProgress.get(destination);
122
+ }
123
+ /**
124
+ * Returns an immutable handle to a compressor in the network.
125
+ */
126
+ getCompressor(client) {
127
+ const compressors = this.compressors;
128
+ const handler = {
129
+ get(_, property) {
130
+ return compressors.get(client)[property];
131
+ },
132
+ set(_, property, value) {
133
+ compressors.get(client)[property] = value;
134
+ return true;
135
+ },
136
+ };
137
+ return new Proxy({}, handler);
138
+ }
139
+ /**
140
+ * Returns a mutable handle to a compressor in the network. Use of mutation methods will break the network invariants and
141
+ * should only be used if the network will not be used again.
142
+ */
143
+ getCompressorUnsafe(client) {
144
+ return this.getCompressor(client);
145
+ }
146
+ /**
147
+ * Returns a mutable handle to a compressor in the network. Use of mutation methods will break the network invariants and
148
+ * should only be used if the network will not be used again. Additionally, the returned compressor will be invalidated/unusable
149
+ * if any network operations cause it to be regenerated (serialization/deserialization, etc.).
150
+ */
151
+ getCompressorUnsafeNoProxy(client) {
152
+ return this.compressors.get(client);
153
+ }
154
+ /**
155
+ * Returns data for all IDs created and received by this client, including ack's of their own (i.e. their own IDs will appear twice)
156
+ */
157
+ getIdLog(client) {
158
+ return this.idLogs.get(client);
159
+ }
160
+ /**
161
+ * Returns data for all IDs received by this client, including ack's of their own.
162
+ */
163
+ getSequencedIdLog(client) {
164
+ return this.sequencedIdLogs.get(client);
165
+ }
166
+ /**
167
+ * Get all compressors for the given destination
168
+ */
169
+ getTargetCompressors(clientTo) {
170
+ return clientTo === MetaClient.All
171
+ ? [...this.compressors.entries()]
172
+ : [[clientTo, this.getCompressor(clientTo)]];
173
+ }
174
+ /**
175
+ * Changes the capacity request amount for a client. It will take effect immediately.
176
+ */
177
+ changeCapacity(client, newClusterCapacity) {
178
+ // eslint-disable-next-line @typescript-eslint/dot-notation
179
+ this.compressors.get(client)["nextRequestedClusterSize"] = newClusterCapacity;
180
+ }
181
+ addNewId(client, id, originatingClient, sessionIdFrom, isSequenced) {
182
+ const idData = {
183
+ id,
184
+ originatingClient,
185
+ sessionId: sessionIdFrom,
186
+ isSequenced,
187
+ };
188
+ const clientIds = this.idLogs.get(client);
189
+ clientIds.push(idData);
190
+ if (isSequenced) {
191
+ const sequencedIds = this.sequencedIdLogs.get(client);
192
+ sequencedIds.push(idData);
193
+ }
194
+ }
195
+ /**
196
+ * Allocates a new range of local IDs and enqueues them for future delivery via a `testIdDelivery` action.
197
+ * Calls to this method determine the total order of delivery, regardless of when `deliverOperations` is called.
198
+ */
199
+ allocateAndSendIds(clientFrom, numIds) {
200
+ return this.allocateAndSendIdsFromRemoteClient(clientFrom, sessionIds.get(clientFrom), numIds);
201
+ }
202
+ /**
203
+ * Same contract as `allocateAndSendIds`, but the originating client will be a client with the supplied sessionId.
204
+ */
205
+ allocateAndSendIdsFromRemoteClient(clientFrom, sessionIdFrom, numIds) {
206
+ assert(numIds > 0, "Must allocate a non-zero number of IDs");
207
+ if (clientFrom === OriginatingClient.Remote) {
208
+ const range = {
209
+ sessionId: sessionIdFrom,
210
+ ids: {
211
+ firstGenCount: 1,
212
+ count: numIds,
213
+ requestedClusterSize: this.getCompressor(Client.Client1)[
214
+ // eslint-disable-next-line @typescript-eslint/dot-notation
215
+ "nextRequestedClusterSize"],
216
+ },
217
+ };
218
+ const opSpaceIds = [];
219
+ for (let i = 0; i < numIds; i++) {
220
+ opSpaceIds.push(-(i + 1));
221
+ }
222
+ this.serverOperations.push([range, opSpaceIds, clientFrom, sessionIdFrom]);
223
+ return opSpaceIds;
224
+ }
225
+ else {
226
+ assert(sessionIdFrom === sessionIds.get(clientFrom));
227
+ const compressor = this.compressors.get(clientFrom);
228
+ const sessionSpaceIds = generateCompressedIds(compressor, numIds);
229
+ for (let i = 0; i < numIds; i++) {
230
+ this.addNewId(clientFrom, sessionSpaceIds[i], clientFrom, sessionIdFrom, false);
231
+ }
232
+ const opSpaceIds = sessionSpaceIds.map((id) => compressor.normalizeToOpSpace(id));
233
+ const creationRange = compressor.takeNextCreationRange();
234
+ this.serverOperations.push([creationRange, opSpaceIds, clientFrom, sessionIdFrom]);
235
+ return opSpaceIds;
236
+ }
237
+ }
238
+ /**
239
+ * Delivers all undelivered ID ranges from the server to the target clients.
240
+ */
241
+ deliverOperations(clientTakingDelivery, opsToDeliver) {
242
+ let opIndexBound;
243
+ if (clientTakingDelivery === DestinationClient.All) {
244
+ assert(opsToDeliver === undefined);
245
+ opIndexBound = this.serverOperations.length;
246
+ }
247
+ else {
248
+ opIndexBound =
249
+ opsToDeliver !== undefined
250
+ ? this.clientProgress.get(clientTakingDelivery) + opsToDeliver
251
+ : this.serverOperations.length;
252
+ }
253
+ for (const [clientTo, compressorTo] of this.getTargetCompressors(clientTakingDelivery)) {
254
+ for (let i = this.clientProgress.get(clientTo); i < opIndexBound; i++) {
255
+ const [range, opSpaceIds, clientFrom, sessionIdFrom] = this.serverOperations[i];
256
+ compressorTo.finalizeCreationRange(range);
257
+ const ids = range.ids;
258
+ if (ids !== undefined) {
259
+ for (const id of opSpaceIds) {
260
+ const sessionSpaceId = compressorTo.normalizeToSessionSpace(id, range.sessionId);
261
+ this.addNewId(clientTo, sessionSpaceId, clientFrom, sessionIdFrom, true);
262
+ }
263
+ }
264
+ }
265
+ this.clientProgress.set(clientTo, opIndexBound);
266
+ }
267
+ }
268
+ /**
269
+ * Simulate a client disconnecting (and serializing), then reconnecting (and deserializing)
270
+ */
271
+ goOfflineThenResume(client) {
272
+ const compressor = this.compressors.get(client);
273
+ const [_, resumedCompressor] = roundtrip(compressor, true);
274
+ this.compressors.set(client, resumedCompressor);
275
+ }
276
+ /**
277
+ * Ensure general validity of the network state. Useful for calling periodically or at the end of test scenarios.
278
+ */
279
+ assertNetworkState() {
280
+ const sequencedLogs = Object.values(Client).map((client) => [this.compressors.get(client), this.getSequencedIdLog(client)]);
281
+ // First, ensure all clients each generated a unique ID for each of their own calls to generate.
282
+ for (const [compressor, ids] of sequencedLogs) {
283
+ const allUuids = new Set();
284
+ for (const idData of ids) {
285
+ const uuid = compressor.decompress(idData.id);
286
+ assert.strictEqual(!allUuids.has(uuid), true, "Duplicate UUID generated.");
287
+ allUuids.add(uuid);
288
+ }
289
+ }
290
+ const maxLogLength = sequencedLogs
291
+ .map(([_, data]) => data.length)
292
+ .reduce((p, n) => Math.max(p, n));
293
+ function getNextLogWithEntryAt(logsIndex, entryIndex) {
294
+ for (let i = logsIndex; i < sequencedLogs.length; i++) {
295
+ const log = sequencedLogs[i];
296
+ if (log[1].length > entryIndex) {
297
+ return i;
298
+ }
299
+ }
300
+ return undefined;
301
+ }
302
+ const uuids = new Set();
303
+ const finalIds = new Set();
304
+ const idIndicesAggregator = new Map();
305
+ function* getIdLogEntries(columnIndex) {
306
+ let current = getNextLogWithEntryAt(0, columnIndex);
307
+ while (current !== undefined) {
308
+ const next = getNextLogWithEntryAt(current + 1, columnIndex);
309
+ const [compressor, log] = sequencedLogs[current];
310
+ if (next === undefined) {
311
+ yield [[compressor, log[columnIndex]]];
312
+ }
313
+ else {
314
+ const [compressorNext, logNext] = sequencedLogs[next];
315
+ yield [
316
+ [compressor, log[columnIndex]],
317
+ [compressorNext, logNext[columnIndex]],
318
+ ];
319
+ }
320
+ current = next;
321
+ }
322
+ }
323
+ for (let i = 0; i < maxLogLength; i++) {
324
+ let idCreatorCount = 0;
325
+ let originatingSession;
326
+ for (const [current, next] of getIdLogEntries(i)) {
327
+ const [compressorA, idDataA] = current;
328
+ const sessionSpaceIdA = idDataA.id;
329
+ const idIndex = getOrCreate(idIndicesAggregator, idDataA.sessionId, () => 0);
330
+ originatingSession ?? (originatingSession = idDataA.sessionId);
331
+ assert(idDataA.sessionId === originatingSession, "Test infra gave wrong originating client to TestIdData");
332
+ // Only one client should have this ID as local in its session space, as only one client could have created this ID
333
+ if (isLocalId(sessionSpaceIdA)) {
334
+ if (originatingSession !== OriginatingClient.Remote) {
335
+ assert.strictEqual(idDataA.sessionId, this.compressors.get(idDataA.originatingClient)
336
+ .localSessionId);
337
+ }
338
+ idCreatorCount++;
339
+ }
340
+ const uuidASessionSpace = compressorA.decompress(sessionSpaceIdA);
341
+ assert.strictEqual(uuidASessionSpace, incrementStableId(idDataA.sessionId, idIndex));
342
+ assert.strictEqual(compressorA.recompress(uuidASessionSpace), sessionSpaceIdA);
343
+ uuids.add(uuidASessionSpace);
344
+ const opSpaceIdA = compressorA.normalizeToOpSpace(sessionSpaceIdA);
345
+ if (!isFinalId(opSpaceIdA)) {
346
+ fail("IDs should have been finalized.");
347
+ }
348
+ const reNormalizedIdA = compressorA.normalizeToSessionSpace(opSpaceIdA, compressorA.localSessionId);
349
+ assert.strictEqual(reNormalizedIdA, sessionSpaceIdA);
350
+ finalIds.add(opSpaceIdA);
351
+ const uuidAOpSpace = compressorA.decompress(reNormalizedIdA);
352
+ assert.strictEqual(uuidASessionSpace, uuidAOpSpace);
353
+ if (next !== undefined) {
354
+ const [compressorB, idDataB] = next;
355
+ const sessionSpaceIdB = idDataB.id;
356
+ const uuidBSessionSpace = compressorB.decompress(sessionSpaceIdB);
357
+ assert.strictEqual(uuidASessionSpace, uuidBSessionSpace);
358
+ const opSpaceIdB = compressorB.normalizeToOpSpace(sessionSpaceIdB);
359
+ if (opSpaceIdA !== opSpaceIdB) {
360
+ compressorB.normalizeToOpSpace(sessionSpaceIdB);
361
+ compressorA.normalizeToOpSpace(sessionSpaceIdA);
362
+ }
363
+ assert.strictEqual(opSpaceIdA, opSpaceIdB);
364
+ if (!isFinalId(opSpaceIdB)) {
365
+ fail("IDs should have been finalized.");
366
+ }
367
+ const uuidBOpSpace = compressorB.decompress(opSpaceIdB);
368
+ assert.strictEqual(uuidAOpSpace, uuidBOpSpace);
369
+ }
370
+ }
371
+ assert(idCreatorCount <= 1, "Only one client can create an ID.");
372
+ assert.strictEqual(uuids.size, finalIds.size);
373
+ assert(originatingSession !== undefined, "Expected originating client to be defined");
374
+ idIndicesAggregator.set(originatingSession,
375
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
376
+ (idIndicesAggregator.get(originatingSession) ??
377
+ fail("Expected pre-existing index for originating client")) + 1);
378
+ }
379
+ for (const [compressor] of sequencedLogs) {
380
+ expectSerializes(compressor);
381
+ }
382
+ }
383
+ }
384
+ export function roundtrip(compressor, withSession) {
385
+ if (withSession) {
386
+ const serialized = compressor.serialize(withSession);
387
+ return [serialized, IdCompressor.deserialize(serialized)];
388
+ }
389
+ const nonLocalSerialized = compressor.serialize(withSession);
390
+ return [nonLocalSerialized, IdCompressor.deserialize(nonLocalSerialized, createSessionId())];
391
+ }
392
+ /**
393
+ * Asserts that the supplied compressor correctly roundtrips through serialization/deserialization.
394
+ */
395
+ export function expectSerializes(compressor) {
396
+ function expectSerializesWithSession(withSession) {
397
+ let serialized;
398
+ let deserialized;
399
+ if (withSession) {
400
+ [serialized, deserialized] = roundtrip(compressor, true);
401
+ }
402
+ else {
403
+ [serialized, deserialized] = roundtrip(compressor, false);
404
+ }
405
+ assert.strictEqual(compressor.equals(deserialized, withSession), true);
406
+ return serialized;
407
+ }
408
+ return [
409
+ expectSerializesWithSession(false),
410
+ expectSerializesWithSession(true),
411
+ ];
412
+ }
413
+ /**
414
+ * Merges 'from' into 'to', and returns 'to'.
415
+ */
416
+ export function mergeArrayMaps(to, from) {
417
+ for (const [key, value] of from.entries()) {
418
+ const entry = to.get(key);
419
+ if (entry !== undefined) {
420
+ entry.push(...value);
421
+ }
422
+ else {
423
+ to.set(key, [...value]);
424
+ }
425
+ }
426
+ return to;
427
+ }
428
+ const defaultOptions = {
429
+ maxClusterSize: 25,
430
+ validateInterval: 200,
431
+ outsideAllocationFraction: 0.1,
432
+ };
433
+ export function makeOpGenerator(options) {
434
+ const { maxClusterSize, validateInterval, outsideAllocationFraction } = {
435
+ ...defaultOptions,
436
+ ...options,
437
+ };
438
+ assert(outsideAllocationFraction >= 0 && outsideAllocationFraction <= 1);
439
+ function allocateIdsGenerator({ activeClients, clusterSize, random, }) {
440
+ const client = random.pick(activeClients);
441
+ const maxIdsPerUsage = clusterSize * 2;
442
+ const numIds = Math.floor(random.real(0, 1) ** 3 * maxIdsPerUsage) + 1;
443
+ return {
444
+ type: "allocateIds",
445
+ client,
446
+ numIds,
447
+ };
448
+ }
449
+ function allocateOutsideIdsGenerator({ clusterSize, random, }) {
450
+ const maxIdsPerUsage = clusterSize * 2;
451
+ const numIds = Math.floor(random.real(0, 1) ** 3 * maxIdsPerUsage) + 1;
452
+ return {
453
+ type: "allocateOutsideIds",
454
+ sessionId: createSessionId(),
455
+ numIds,
456
+ };
457
+ }
458
+ function changeCapacityGenerator({ random, activeClients }) {
459
+ return {
460
+ type: "changeCapacity",
461
+ client: random.pick(activeClients),
462
+ newSize: Math.min(Math.floor(random.real(0, 1) ** 2 * maxClusterSize) + 1, maxClusterSize),
463
+ };
464
+ }
465
+ function deliverAllOperationsGenerator() {
466
+ return {
467
+ type: "deliverAllOperations",
468
+ };
469
+ }
470
+ function deliverSomeOperationsGenerator({ random, selectableClients, network, }) {
471
+ const pendingClients = selectableClients.filter((c) => network.getPendingOperations(c) > 0);
472
+ if (pendingClients.length === 0) {
473
+ return {
474
+ type: "deliverSomeOperations",
475
+ client: random.pick(selectableClients),
476
+ count: 0,
477
+ };
478
+ }
479
+ const client = random.pick(pendingClients);
480
+ return {
481
+ type: "deliverSomeOperations",
482
+ client,
483
+ count: random.integer(1, network.getPendingOperations(client)),
484
+ };
485
+ }
486
+ function reconnectGenerator({ activeClients, random }) {
487
+ return { type: "reconnect", client: random.pick(activeClients) };
488
+ }
489
+ const allocationWeight = 16;
490
+ return interleave(createWeightedGenerator([
491
+ [changeCapacityGenerator, 1],
492
+ [allocateIdsGenerator, Math.round(allocationWeight * (1 - outsideAllocationFraction))],
493
+ [allocateOutsideIdsGenerator, Math.round(allocationWeight * outsideAllocationFraction)],
494
+ [deliverAllOperationsGenerator, 2],
495
+ [deliverSomeOperationsGenerator, 6],
496
+ [reconnectGenerator, 1],
497
+ ]), take(1, repeat({ type: "validate" })), validateInterval);
498
+ }
499
+ /**
500
+ * Performs random actions on a test network.
501
+ * @param generator - the generator used to provide operations
502
+ * @param network - the test network to test
503
+ * @param seed - the seed for the random generation of the fuzz actions
504
+ * @param observerClient - if provided, this client will never generate local ids
505
+ * @param synchronizeAtEnd - if provided, all client will have all operations delivered from the server at the end of the test
506
+ * @param validator - if provided, this callback will be invoked periodically during the fuzz test.
507
+ */
508
+ export function performFuzzActions(generator, network, seed, observerClient, synchronizeAtEnd = true, validator, saveInfo) {
509
+ const random = makeRandom(seed);
510
+ const selectableClients = network
511
+ .getTargetCompressors(MetaClient.All)
512
+ .map(([client]) => client);
513
+ const initialState = {
514
+ random,
515
+ network,
516
+ activeClients: selectableClients.filter((c) => c !== observerClient),
517
+ selectableClients,
518
+ clusterSize: network.initialClusterSize,
519
+ };
520
+ performFuzzActionsBase(generator, {
521
+ allocateIds: (state, { client, numIds }) => {
522
+ network.allocateAndSendIdsFromRemoteClient(client, sessionIds.get(client), numIds);
523
+ return state;
524
+ },
525
+ allocateOutsideIds: (state, { sessionId, numIds }) => {
526
+ network.allocateAndSendIdsFromRemoteClient(OriginatingClient.Remote, sessionId, numIds);
527
+ return state;
528
+ },
529
+ changeCapacity: (state, op) => {
530
+ network.changeCapacity(op.client, op.newSize);
531
+ return { ...state, clusterSize: op.newSize };
532
+ },
533
+ deliverSomeOperations: (state, op) => {
534
+ network.deliverOperations(op.client, op.count);
535
+ return state;
536
+ },
537
+ deliverAllOperations: (state) => {
538
+ network.deliverOperations(DestinationClient.All);
539
+ return state;
540
+ },
541
+ reconnect: (state, { client }) => {
542
+ network.goOfflineThenResume(client);
543
+ return state;
544
+ },
545
+ validate: (state) => {
546
+ network.deliverOperations(DestinationClient.All);
547
+ validator?.(network);
548
+ return state;
549
+ },
550
+ }, initialState, saveInfo);
551
+ if (synchronizeAtEnd) {
552
+ network.deliverOperations(DestinationClient.All);
553
+ validator?.(network);
554
+ }
555
+ }
556
+ /**
557
+ * Helper to generate a fixed number of IDs.
558
+ */
559
+ export function generateCompressedIds(compressor, count) {
560
+ const ids = [];
561
+ for (let i = 0; i < count; i++) {
562
+ ids.push(compressor.generateCompressedId());
563
+ }
564
+ return ids;
565
+ }
566
+ export function createAlwaysFinalizedIdCompressor(sessionIdOrLogger, loggerOrUndefined) {
567
+ const compressor = sessionIdOrLogger === undefined
568
+ ? createIdCompressor()
569
+ : typeof sessionIdOrLogger === "string"
570
+ ? createIdCompressor(sessionIdOrLogger, loggerOrUndefined)
571
+ : createIdCompressor(sessionIdOrLogger);
572
+ // Permanently put the compressor in a ghost session
573
+ compressor.startGhostSession(createSessionId());
574
+ return compressor;
575
+ }
576
+ //# sourceMappingURL=idCompressorTestUtilities.js.map