@fluid-experimental/tree 0.59.3003 → 0.59.4000

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 (54) hide show
  1. package/dist/Forest.js +1 -1
  2. package/dist/Forest.js.map +1 -1
  3. package/dist/id-compressor/IdCompressor.d.ts +19 -45
  4. package/dist/id-compressor/IdCompressor.d.ts.map +1 -1
  5. package/dist/id-compressor/IdCompressor.js +151 -151
  6. package/dist/id-compressor/IdCompressor.js.map +1 -1
  7. package/dist/id-compressor/SessionIdNormalizer.d.ts +1 -16
  8. package/dist/id-compressor/SessionIdNormalizer.d.ts.map +1 -1
  9. package/dist/id-compressor/SessionIdNormalizer.js +23 -21
  10. package/dist/id-compressor/SessionIdNormalizer.js.map +1 -1
  11. package/dist/id-compressor/persisted-types/0.0.1.d.ts +23 -4
  12. package/dist/id-compressor/persisted-types/0.0.1.d.ts.map +1 -1
  13. package/dist/id-compressor/persisted-types/0.0.1.js.map +1 -1
  14. package/lib/Forest.js +1 -1
  15. package/lib/Forest.js.map +1 -1
  16. package/lib/id-compressor/IdCompressor.d.ts +19 -45
  17. package/lib/id-compressor/IdCompressor.d.ts.map +1 -1
  18. package/lib/id-compressor/IdCompressor.js +152 -152
  19. package/lib/id-compressor/IdCompressor.js.map +1 -1
  20. package/lib/id-compressor/SessionIdNormalizer.d.ts +1 -16
  21. package/lib/id-compressor/SessionIdNormalizer.d.ts.map +1 -1
  22. package/lib/id-compressor/SessionIdNormalizer.js +23 -21
  23. package/lib/id-compressor/SessionIdNormalizer.js.map +1 -1
  24. package/lib/id-compressor/persisted-types/0.0.1.d.ts +23 -4
  25. package/lib/id-compressor/persisted-types/0.0.1.d.ts.map +1 -1
  26. package/lib/id-compressor/persisted-types/0.0.1.js.map +1 -1
  27. package/lib/test/IdCompressor.perf.tests.js +47 -67
  28. package/lib/test/IdCompressor.perf.tests.js.map +1 -1
  29. package/lib/test/IdCompressor.tests.js +196 -101
  30. package/lib/test/IdCompressor.tests.js.map +1 -1
  31. package/lib/test/Summary.tests.d.ts +0 -1
  32. package/lib/test/Summary.tests.d.ts.map +1 -1
  33. package/lib/test/Summary.tests.js +0 -3
  34. package/lib/test/Summary.tests.js.map +1 -1
  35. package/lib/test/Virtualization.tests.js +0 -4
  36. package/lib/test/Virtualization.tests.js.map +1 -1
  37. package/lib/test/utilities/IdCompressorTestUtilities.d.ts +14 -7
  38. package/lib/test/utilities/IdCompressorTestUtilities.d.ts.map +1 -1
  39. package/lib/test/utilities/IdCompressorTestUtilities.js +40 -20
  40. package/lib/test/utilities/IdCompressorTestUtilities.js.map +1 -1
  41. package/lib/test/utilities/SharedTreeTests.d.ts.map +1 -1
  42. package/lib/test/utilities/SharedTreeTests.js +0 -1
  43. package/lib/test/utilities/SharedTreeTests.js.map +1 -1
  44. package/lib/test/utilities/SummaryLoadPerfTests.js +1 -1
  45. package/lib/test/utilities/SummaryLoadPerfTests.js.map +1 -1
  46. package/lib/test/utilities/TestCommon.d.ts +4 -0
  47. package/lib/test/utilities/TestCommon.d.ts.map +1 -1
  48. package/lib/test/utilities/TestCommon.js +6 -0
  49. package/lib/test/utilities/TestCommon.js.map +1 -1
  50. package/package.json +24 -19
  51. package/src/Forest.ts +1 -1
  52. package/src/id-compressor/IdCompressor.ts +171 -198
  53. package/src/id-compressor/SessionIdNormalizer.ts +29 -41
  54. package/src/id-compressor/persisted-types/0.0.1.ts +25 -4
@@ -15,6 +15,7 @@ const UuidUtilities_1 = require("../UuidUtilities");
15
15
  const AppendOnlySortedMap_1 = require("./AppendOnlySortedMap");
16
16
  const IdRange_1 = require("./IdRange");
17
17
  const NumericUuid_1 = require("./NumericUuid");
18
+ const SessionIdNormalizer_1 = require("./SessionIdNormalizer");
18
19
  /**
19
20
  * Roughly equates to a minimum of 1M sessions before we start allocating 64 bit IDs.
20
21
  * This value must *NOT* change without careful consideration to compatibility.
@@ -144,11 +145,21 @@ class IdCompressor {
144
145
  */
145
146
  this.localOverrides = new AppendOnlySortedMap_1.AppendOnlySortedMap(Common_1.compareFiniteNumbersReversed);
146
147
  /**
147
- * Maps local IDs to the cluster they belong to (if any). This can be used to efficiently convert a local ID to a
148
- * final ID by finding an entry \<= a given local ID (to find the cluster it is associated with) and checking
149
- * it against `numFinalizedLocalIds`.
148
+ * Maps local IDs to the final ID they are associated with (if any), and maps final IDs to the corresponding local ID (if any).
149
+ * This is used to efficiently compute normalization. This map can be thought of as mapping ranges of "optimistic uncertainty"
150
+ * (local IDs) to the result of consensus (reserved ranges of final IDs, a.k.a. clusters). Any given range of local IDs
151
+ * does not necessarily span an entire cluster, as some session-space IDs may be allocated *after* a cluster has been allocated
152
+ * but before it is full. In this case, there is no uncertainty, as the range of final IDs was reserved when the cluster was created.
153
+ * However, there is always a range of local IDs with size \>= 1 associated with the beginning of every cluster, as clusters are only
154
+ * created *after* they are needed and thus there is some period of uncertainty after local IDs have been handed out but before the
155
+ * range containing them has been finalized. There may also be ranges of local IDs that do not start at the beginning of a
156
+ * cluster; this happens when a cluster is expanded instead of allocating a new one.
157
+ * Additionally, session space IDs associated with an override string will also always be local IDs, because there is uncertainty as
158
+ * to whether another client simultaneously allocated the same override and could get sequenced first (a.k.a. unification) and its
159
+ * final ID would be associated with that override.
160
+ * See `SessionIdNormalizer` for more.
150
161
  */
151
- this.localIdToCluster = new AppendOnlySortedMap_1.AppendOnlyDoublySortedMap(Common_1.compareFiniteNumbersReversed, (value) => value[0], Common_1.compareFiniteNumbers);
162
+ this.sessionIdNormalizer = new SessionIdNormalizer_1.SessionIdNormalizer();
152
163
  /**
153
164
  * Contains entries for cluster base UUIDs and override strings (both local and final).
154
165
  * As a performance optimization, entries for finalized strings also include the containing cluster object.
@@ -204,12 +215,6 @@ class IdCompressor {
204
215
  get attributionId() {
205
216
  return this.localSession.attributionId;
206
217
  }
207
- /**
208
- * Helper comparator for searching append-only sorted maps.
209
- */
210
- static overrideComparator(search, element) {
211
- return (0, Common_1.compareFiniteNumbers)(search, element[0]);
212
- }
213
218
  /**
214
219
  * Creates a session object for the supplied ID.
215
220
  * Must only be called once per ID.
@@ -247,77 +252,28 @@ class IdCompressor {
247
252
  /**
248
253
  * Returns an iterable of all IDs created by this compressor.
249
254
  */
250
- *getAllIdsFromLocalSession() {
251
- // TODO: this will change when final IDs are returned eagerly
252
- for (let i = 1; i <= this.localIdCount; i++) {
253
- yield -i;
254
- }
255
+ getAllIdsFromLocalSession() {
256
+ return this.sessionIdNormalizer[Symbol.iterator]();
255
257
  }
256
258
  /**
257
259
  * Returns the attribution ID associated with the compressor that created the ID
258
260
  */
259
261
  attributeId(id) {
260
- var _a;
261
262
  const opSpaceNormalizedId = this.normalizeToOpSpace(id);
262
263
  if (isLocalId(opSpaceNormalizedId)) {
263
264
  return this.attributionId;
264
265
  }
265
- const [_, cluster] = (_a = this.getClusterForFinalId(opSpaceNormalizedId)) !== null && _a !== void 0 ? _a : (0, Common_1.fail)('Cluster does not exist for final ID');
266
- return cluster.session.attributionId;
267
- }
268
- /**
269
- * Provides the session-space IDs corresponding to a range of IDs.
270
- * See `IdRange` for more details.
271
- */
272
- getIdsFromRange(rangeDescriptor, sessionId) {
273
- var _a, _b, _c;
274
- const { first, count } = rangeDescriptor;
275
- if (sessionId === this.localSessionId) {
276
- return {
277
- length: count,
278
- get: (index) => {
279
- if (index < 0 || index >= count) {
280
- (0, Common_1.fail)('Index out of bounds of range.');
281
- }
282
- return (first - index);
283
- },
284
- };
285
- }
286
- else {
287
- const session = (_a = this.sessions.get(sessionId)) !== null && _a !== void 0 ? _a : (0, Common_1.fail)('Unknown session, range may not be finalized.');
288
- const firstNumericUuid = (0, NumericUuid_1.incrementUuid)(session.sessionUuid, -first - 1);
289
- const firstFinal = (_b = this.compressNumericUuid(firstNumericUuid)) !== null && _b !== void 0 ? _b : (0, Common_1.fail)('Remote range must be finalized before getting IDs.');
290
- (0, Common_1.assert)(isFinalId(firstFinal), 'ID from a remote session ID must have final form, as overrides are impossible by definition.');
291
- const [baseFinalId, cluster] = (_c = this.getClusterForFinalId(firstFinal)) !== null && _c !== void 0 ? _c : (0, Common_1.fail)();
292
- const numIdsRemainingInFirstCluster = cluster.capacity - (firstFinal - baseFinalId);
293
- let pivotFinal;
294
- if (count > numIdsRemainingInFirstCluster) {
295
- const compressedPivot = this.compressNumericUuid((0, NumericUuid_1.incrementUuid)(firstNumericUuid, numIdsRemainingInFirstCluster));
296
- // Looking up the actual cluster can be avoided, as it is guaranteed that at most one new cluster will be
297
- // created when finalizing a range (regardless of size) due to the expansion optimization.
298
- if (compressedPivot === undefined || isLocalId(compressedPivot)) {
299
- (0, Common_1.fail)('ID from a remote session ID must have final form, as overrides are impossible by definition.');
300
- }
301
- else {
302
- pivotFinal = compressedPivot;
303
- }
266
+ const closestCluster = this.getClusterForFinalId(opSpaceNormalizedId);
267
+ if (closestCluster === undefined) {
268
+ if (this.sessionIdNormalizer.getCreationIndex(opSpaceNormalizedId) !== undefined) {
269
+ return this.attributionId;
270
+ }
271
+ else {
272
+ (0, Common_1.fail)('Cluster does not exist for final ID');
304
273
  }
305
- return {
306
- length: count,
307
- get: (index) => {
308
- if (index < 0 || index >= count) {
309
- (0, Common_1.fail)('Index out of bounds of range.');
310
- }
311
- if (index < numIdsRemainingInFirstCluster) {
312
- return (firstFinal + index);
313
- }
314
- else {
315
- return ((pivotFinal !== null && pivotFinal !== void 0 ? pivotFinal : (0, Common_1.fail)('Pivot must exist if range spans clusters.')) +
316
- (index - numIdsRemainingInFirstCluster));
317
- }
318
- },
319
- };
320
274
  }
275
+ const [_, cluster] = closestCluster;
276
+ return cluster.session.attributionId;
321
277
  }
322
278
  /**
323
279
  * Returns a range of local IDs created by this session in a format for sending to the server for finalizing.
@@ -373,7 +329,7 @@ class IdCompressor {
373
329
  * @param range - the range of session-local IDs to finalize.
374
330
  */
375
331
  finalizeCreationRange(range) {
376
- var _a, _b, _c;
332
+ var _a, _b, _c, _d;
377
333
  const { sessionId, attributionId } = range;
378
334
  const isLocal = sessionId === this.localSessionId;
379
335
  const session = (_a = this.sessions.get(sessionId)) !== null && _a !== void 0 ? _a : this.createSession(sessionId, attributionId);
@@ -387,16 +343,26 @@ class IdCompressor {
387
343
  cluster: undefined,
388
344
  clusterBase: undefined,
389
345
  };
390
- const normalizedLastFinalized = (_b = session.lastFinalizedLocalId) !== null && _b !== void 0 ? _b : 0;
391
- const { first: newFirstFinalizedLocalId, last: newLastFinalizedLocalId } = ids;
392
- (0, Common_1.assert)(newFirstFinalizedLocalId === normalizedLastFinalized - 1, 'Ranges finalized out of order.');
346
+ const currentClusterExists = currentCluster !== undefined && currentBaseFinalId !== undefined;
347
+ const normalizedLastFinalizedLocal = (_b = session.lastFinalizedLocalId) !== null && _b !== void 0 ? _b : 0;
348
+ const { first: newFirstFinalizedLocal, last: newLastFinalizedLocal } = ids;
349
+ (0, Common_1.assert)(newFirstFinalizedLocal === normalizedLastFinalizedLocal - 1, 'Ranges finalized out of order.');
393
350
  // The total number of session-local IDs to finalize
394
- const finalizeCount = normalizedLastFinalized - newLastFinalizedLocalId;
351
+ const finalizeCount = normalizedLastFinalizedLocal - newLastFinalizedLocal;
395
352
  (0, Common_1.assert)(finalizeCount >= 1, 'Cannot finalize an empty range.');
396
353
  let initialClusterCount = 0;
397
354
  let remainingCount = finalizeCount;
398
355
  let newBaseUuid;
399
- if (currentCluster !== undefined && currentBaseFinalId !== undefined) {
356
+ if (currentClusterExists) {
357
+ if (isLocal) {
358
+ const lastKnownFinal = (_c = this.sessionIdNormalizer.getLastFinalId()) !== null && _c !== void 0 ? _c : (0, Common_1.fail)('Cluster exists but normalizer does not have an entry for it.');
359
+ const lastFinalInCluster = (currentBaseFinalId +
360
+ Math.min(currentCluster.count + finalizeCount, currentCluster.capacity) -
361
+ 1);
362
+ if (lastFinalInCluster > lastKnownFinal) {
363
+ this.sessionIdNormalizer.addFinalIds((lastKnownFinal + 1), lastFinalInCluster, currentCluster);
364
+ }
365
+ }
400
366
  initialClusterCount = currentCluster.count;
401
367
  const remainingCapacity = currentCluster.capacity - initialClusterCount;
402
368
  const overflow = remainingCount - remainingCapacity;
@@ -413,6 +379,21 @@ class IdCompressor {
413
379
  this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId + expansionAmount);
414
380
  (0, Common_1.assert)(this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER, 'The number of allocated final IDs must not exceed the JS maximum safe integer.');
415
381
  this.checkClusterForCollision(currentCluster);
382
+ if (isLocal) {
383
+ // Example with cluster size of 3:
384
+ // Ids generated so far: -1 1 2 -4 -5 <-- note positive numbers are eager finals
385
+ // Cluster: [ 0 1 2 ]
386
+ // ~ finalizing happens, causing expansion ~
387
+ // Cluster: [ 0 1 2 3 4 5 ]
388
+ // corresponding locals: -1 -4
389
+ // lastFinalizedLocalId^ ^newLastFinalizedLocalId = -6
390
+ // overflow = 2: ----
391
+ // localIdPivot^
392
+ // lastFinalizedFinal^
393
+ const lastFinalizedFinal = (currentBaseFinalId + currentCluster.count - 1);
394
+ const finalPivot = (lastFinalizedFinal - overflow + 1);
395
+ this.sessionIdNormalizer.addFinalIds(finalPivot, lastFinalizedFinal, currentCluster);
396
+ }
416
397
  }
417
398
  }
418
399
  else {
@@ -453,9 +434,10 @@ class IdCompressor {
453
434
  session,
454
435
  };
455
436
  const usedCapacity = finalizeCount - remainingCount;
456
- localIdPivot = (newFirstFinalizedLocalId - usedCapacity);
437
+ localIdPivot = (newFirstFinalizedLocal - usedCapacity);
457
438
  if (isLocal) {
458
- this.localIdToCluster.append(localIdPivot, [newBaseFinalId, newCluster]);
439
+ const lastFinalizedFinal = (newBaseFinalId + newCluster.count - 1);
440
+ this.sessionIdNormalizer.addFinalIds(newBaseFinalId, lastFinalizedFinal, newCluster);
459
441
  }
460
442
  this.checkClusterForCollision(newCluster);
461
443
  this.clustersAndOverridesInversion.set((0, NumericUuid_1.stableIdFromNumericUuid)(newCluster.baseUuid), {
@@ -474,8 +456,8 @@ class IdCompressor {
474
456
  const [overriddenLocal, override] = overrides[i];
475
457
  // Note: recall that local IDs are negative
476
458
  (0, Common_1.assert)(i === 0 || overriddenLocal < overrides[i - 1][0], 'Override IDs must be in sorted order.');
477
- (0, Common_1.assert)(overriddenLocal < normalizedLastFinalized, 'Ranges finalized out of order.');
478
- (0, Common_1.assert)(overriddenLocal >= newLastFinalizedLocalId, 'Malformed range: override ID ahead of range start.');
459
+ (0, Common_1.assert)(overriddenLocal < normalizedLastFinalizedLocal, 'Ranges finalized out of order.');
460
+ (0, Common_1.assert)(overriddenLocal >= newLastFinalizedLocal, 'Malformed range: override ID ahead of range start.');
479
461
  let cluster;
480
462
  let overriddenFinal;
481
463
  if (localIdPivot !== undefined && overriddenLocal <= localIdPivot) {
@@ -490,10 +472,10 @@ class IdCompressor {
490
472
  cluster = currentCluster;
491
473
  overriddenFinal = (currentBaseFinalId +
492
474
  initialClusterCount +
493
- (normalizedLastFinalized - overriddenLocal) -
475
+ (normalizedLastFinalizedLocal - overriddenLocal) -
494
476
  1);
495
477
  }
496
- (_c = cluster.overrides) !== null && _c !== void 0 ? _c : (cluster.overrides = new Map());
478
+ (_d = cluster.overrides) !== null && _d !== void 0 ? _d : (cluster.overrides = new Map());
497
479
  const inversionKey = IdCompressor.createInversionKey(override);
498
480
  const existingIds = this.getExistingIdsForNewOverride(inversionKey, true);
499
481
  let overrideForCluster;
@@ -559,7 +541,7 @@ class IdCompressor {
559
541
  }
560
542
  }
561
543
  }
562
- session.lastFinalizedLocalId = newLastFinalizedLocalId;
544
+ session.lastFinalizedLocalId = newLastFinalizedLocal;
563
545
  }
564
546
  checkClusterForCollision(cluster) {
565
547
  const maxClusterUuid = (0, NumericUuid_1.incrementUuid)(cluster.baseUuid, cluster.capacity - 1);
@@ -639,9 +621,9 @@ class IdCompressor {
639
621
  }
640
622
  const override = (_a = numericOverride !== null && numericOverride !== void 0 ? numericOverride : stableOverride) !== null && _a !== void 0 ? _a : (IdCompressor.isStableInversionKey(inversionKey) ? inversionKey : undefined);
641
623
  if (override !== undefined) {
642
- const localId = this.getLocalIdForStableId(override);
643
- if (localId !== undefined) {
644
- return localId;
624
+ const sessionSpaceId = this.getCompressedIdForStableId(override);
625
+ if (sessionSpaceId !== undefined) {
626
+ return sessionSpaceId;
645
627
  }
646
628
  }
647
629
  return undefined;
@@ -683,41 +665,50 @@ class IdCompressor {
683
665
  * @returns an existing ID if one already exists for `override`, and a new local ID otherwise. The returned ID is in session space.
684
666
  */
685
667
  generateCompressedId(override) {
686
- // If any ID exists for this override (locally or remotely allocated), return it (after ensuring it is in session-space).
668
+ let overrideInversionKey;
687
669
  if (override !== undefined) {
688
- const inversionKey = IdCompressor.createInversionKey(override);
689
- const existingIds = this.getExistingIdsForNewOverride(inversionKey, false);
670
+ overrideInversionKey = IdCompressor.createInversionKey(override);
671
+ const existingIds = this.getExistingIdsForNewOverride(overrideInversionKey, false);
690
672
  if (existingIds !== undefined) {
691
673
  return typeof existingIds === 'number' ? existingIds : existingIds[0];
692
674
  }
693
- else {
694
- const newLocalId = this.generateNextLocalId();
695
- this.localOverrides.append(newLocalId, override);
696
- // Since the local ID was just created, it is in both session and op space
697
- const compressionMapping = newLocalId;
698
- this.clustersAndOverridesInversion.set(inversionKey, compressionMapping);
699
- return newLocalId;
675
+ }
676
+ // Bump local counter regardless, then attempt to optimistically return a final ID.
677
+ // If the local session has reserved a cluster range via consensus, it is safe to hand out final IDs prior to
678
+ // finalizing the range that includes these locals.
679
+ const newLocalId = -++this.localIdCount;
680
+ const { currentClusterDetails } = this.localSession;
681
+ const { sessionIdNormalizer } = this;
682
+ let eagerFinalId;
683
+ let cluster;
684
+ if (currentClusterDetails !== undefined) {
685
+ const { clusterBase } = currentClusterDetails;
686
+ cluster = currentClusterDetails.cluster;
687
+ const lastFinalKnown = sessionIdNormalizer.getLastFinalId();
688
+ if (lastFinalKnown !== undefined && lastFinalKnown - clusterBase + 1 < cluster.capacity) {
689
+ eagerFinalId = (lastFinalKnown + 1);
700
690
  }
701
691
  }
692
+ if (overrideInversionKey !== undefined) {
693
+ const registeredLocal = sessionIdNormalizer.addLocalId();
694
+ (0, Common_1.assert)(registeredLocal === newLocalId, 'TODO');
695
+ if (eagerFinalId !== undefined) {
696
+ sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster !== null && cluster !== void 0 ? cluster : (0, Common_1.fail)());
697
+ }
698
+ this.localOverrides.append(newLocalId, override !== null && override !== void 0 ? override : (0, Common_1.fail)());
699
+ // Since the local ID was just created, it is in both session and op space
700
+ const compressionMapping = newLocalId;
701
+ this.clustersAndOverridesInversion.set(overrideInversionKey, compressionMapping);
702
+ }
703
+ else if (eagerFinalId !== undefined) {
704
+ sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster !== null && cluster !== void 0 ? cluster : (0, Common_1.fail)());
705
+ return eagerFinalId;
706
+ }
702
707
  else {
703
- return this.generateNextLocalId();
708
+ const registeredLocal = sessionIdNormalizer.addLocalId();
709
+ (0, Common_1.assert)(registeredLocal === newLocalId, 'TODO');
704
710
  }
705
- }
706
- /**
707
- * Generates a range of compressed IDs.
708
- * This should ONLY be called to generate IDs for local operations.
709
- * @param count - the number of IDs to generate, must be \> 0.
710
- * @returns a persistable descriptor of the ID range.
711
- */
712
- generateCompressedIdRange(count) {
713
- (0, Common_1.assert)(count > 0, 'Must generate a nonzero number of IDs.');
714
- (0, Common_1.assert)(count <= Number.MAX_SAFE_INTEGER, 'The number of allocated local IDs must not exceed the JS maximum safe integer.');
715
- const first = this.generateNextLocalId();
716
- this.localIdCount += count - 1;
717
- return { first, count };
718
- }
719
- generateNextLocalId() {
720
- return -++this.localIdCount;
711
+ return newLocalId;
721
712
  }
722
713
  /**
723
714
  * Decompresses a previously compressed ID into a UUID or override string.
@@ -738,6 +729,11 @@ class IdCompressor {
738
729
  if (isFinalId(id)) {
739
730
  const possibleCluster = this.getClusterForFinalId(id);
740
731
  if (possibleCluster === undefined) {
732
+ // It may be an unfinalized eager final ID, so check with normalizer to get the offset from the session UUID
733
+ const creationIndex = this.sessionIdNormalizer.getCreationIndex(id);
734
+ if (creationIndex !== undefined) {
735
+ return (0, NumericUuid_1.stableIdFromNumericUuid)(this.localSession.sessionUuid, creationIndex);
736
+ }
741
737
  return undefined;
742
738
  }
743
739
  else {
@@ -805,9 +801,6 @@ class IdCompressor {
805
801
  return compressionMapping;
806
802
  }
807
803
  else {
808
- const cluster = compressionMapping.cluster;
809
- (0, Common_1.assert)(IdCompressor.tryGetOverride(cluster, compressionMapping.originalOverridingFinal) !==
810
- undefined, 'No override for cluster marked as having one.');
811
804
  return ((_a = compressionMapping.associatedLocalId) !== null && _a !== void 0 ? _a : compressionMapping.originalOverridingFinal);
812
805
  }
813
806
  }
@@ -836,9 +829,9 @@ class IdCompressor {
836
829
  }
837
830
  if (isStable) {
838
831
  // May have already computed the numeric UUID, so avoid recomputing if possible
839
- const localId = this.getLocalIdForStableId(numericUuid !== null && numericUuid !== void 0 ? numericUuid : inversionKey);
840
- if (localId !== undefined) {
841
- return localId;
832
+ const sessionSpaceId = this.getCompressedIdForStableId(numericUuid !== null && numericUuid !== void 0 ? numericUuid : inversionKey);
833
+ if (sessionSpaceId !== undefined) {
834
+ return sessionSpaceId;
842
835
  }
843
836
  }
844
837
  return undefined;
@@ -857,9 +850,14 @@ class IdCompressor {
857
850
  if (-id > this.localIdCount) {
858
851
  (0, Common_1.fail)('Supplied local ID was not created by this compressor.');
859
852
  }
860
- // Check if this local ID has not been finalized yet
853
+ // Check if this local ID has not been finalized yet.
854
+ // Comparing lastFinalizedLocalId is a safe check for eager final IDs because the local IDs corresponding to them
855
+ // are never handed out to a consumer, and thus could not be passed into this method.
861
856
  const { lastFinalizedLocalId } = this.localSession;
862
857
  if (lastFinalizedLocalId === undefined || id < lastFinalizedLocalId) {
858
+ // Eager final IDs do not have overrides in the cluster until finalizing
859
+ // This means that using the normalizer to get the final/cluster associated would succeed but would not have the override,
860
+ // so checking localOverrides first is necessary.
863
861
  const override = this.localOverrides.get(id);
864
862
  if (override !== undefined) {
865
863
  const inversionKey = IdCompressor.createInversionKey(override);
@@ -872,8 +870,7 @@ class IdCompressor {
872
870
  }
873
871
  return id;
874
872
  }
875
- const [localBase, [finalBase, cluster]] = (_b = this.localIdToCluster.getPairOrNextLower(id)) !== null && _b !== void 0 ? _b : (0, Common_1.fail)('Locally created cluster should be added to the map when allocated');
876
- const correspondingFinal = (finalBase + (localBase - id));
873
+ const [correspondingFinal, cluster] = (_b = this.sessionIdNormalizer.getFinalId(id)) !== null && _b !== void 0 ? _b : (0, Common_1.fail)('Locally created cluster should be added to the map when allocated');
877
874
  if (cluster.overrides) {
878
875
  const override = cluster.overrides.get(correspondingFinal);
879
876
  if (typeof override === 'object' && override.originalOverridingFinal !== undefined) {
@@ -883,11 +880,10 @@ class IdCompressor {
883
880
  }
884
881
  return correspondingFinal;
885
882
  }
886
- normalizeToSessionSpace(id, originSessionId) {
887
- var _a, _b, _c;
888
- const isLocalSession = originSessionId === this.localSessionId;
883
+ normalizeToSessionSpace(id, sessionIdIfLocal) {
884
+ var _a, _b, _c, _d;
889
885
  if (isLocalId(id)) {
890
- if (isLocalSession) {
886
+ if (sessionIdIfLocal === undefined || sessionIdIfLocal === this.localSessionId) {
891
887
  const localIndex = -id;
892
888
  if (localIndex > this.localIdCount) {
893
889
  (0, Common_1.fail)('Supplied local ID was not created by this compressor.');
@@ -895,27 +891,20 @@ class IdCompressor {
895
891
  return id;
896
892
  }
897
893
  else {
898
- const session = this.sessions.get(originSessionId !== null && originSessionId !== void 0 ? originSessionId : (0, Common_1.fail)());
899
- if (session === undefined) {
900
- (0, Common_1.fail)('No IDs have ever been finalized by the supplied session.');
901
- }
894
+ const session = (_a = this.sessions.get(sessionIdIfLocal)) !== null && _a !== void 0 ? _a : (0, Common_1.fail)('No IDs have ever been finalized by the supplied session.');
902
895
  const localCount = -id;
903
896
  const numericUuid = (0, NumericUuid_1.incrementUuid)(session.sessionUuid, localCount - 1);
904
- return (_a = this.compressNumericUuid(numericUuid)) !== null && _a !== void 0 ? _a : (0, Common_1.fail)('ID is not known to this compressor.');
897
+ return (_b = this.compressNumericUuid(numericUuid)) !== null && _b !== void 0 ? _b : (0, Common_1.fail)('ID is not known to this compressor.');
905
898
  }
906
899
  }
907
- const closestResult = this.localIdToCluster.getPairOrNextLowerByValue(id);
908
- if (closestResult !== undefined) {
909
- const [localBase, [finalBase, cluster]] = closestResult;
910
- const indexInCluster = id - finalBase;
911
- if (indexInCluster < cluster.count) {
912
- return (localBase - indexInCluster);
913
- }
900
+ const normalizedId = this.sessionIdNormalizer.getSessionSpaceId(id);
901
+ if (normalizedId !== undefined) {
902
+ return normalizedId;
914
903
  }
915
904
  // Check for a unified override finalized first by another session but to which the local session
916
905
  // still has an associated local ID.
917
- const [_, cluster] = (_b = this.getClusterForFinalId(id)) !== null && _b !== void 0 ? _b : (0, Common_1.fail)('Supplied final ID was not finalized by this compressor.');
918
- const override = (_c = cluster.overrides) === null || _c === void 0 ? void 0 : _c.get(id);
906
+ const [_, cluster] = (_c = this.getClusterForFinalId(id)) !== null && _c !== void 0 ? _c : (0, Common_1.fail)('Supplied final ID was not finalized by this compressor.');
907
+ const override = (_d = cluster.overrides) === null || _d === void 0 ? void 0 : _d.get(id);
919
908
  if (typeof override === 'object' && override.associatedLocalId !== undefined) {
920
909
  return override.associatedLocalId;
921
910
  }
@@ -949,13 +938,19 @@ class IdCompressor {
949
938
  }
950
939
  return sessionSpaceId;
951
940
  }
952
- getLocalIdForStableId(stableId) {
941
+ /**
942
+ * Returns a compressed ID for the supplied stable ID if it was created by the local session, and undefined otherwise.
943
+ */
944
+ getCompressedIdForStableId(stableId) {
953
945
  const numericUuid = typeof stableId === 'string' ? (0, NumericUuid_1.numericUuidFromStableId)(stableId) : stableId;
954
- const offset = (0, NumericUuid_1.getPositiveDelta)(numericUuid, this.localSession.sessionUuid, this.localIdCount - 1);
955
- if (offset === undefined) {
956
- return undefined;
946
+ const creationIndex = (0, NumericUuid_1.getPositiveDelta)(numericUuid, this.localSession.sessionUuid, this.localIdCount - 1);
947
+ if (creationIndex !== undefined) {
948
+ const sessionSpaceId = this.sessionIdNormalizer.getIdByCreationIndex(creationIndex);
949
+ if (sessionSpaceId !== undefined) {
950
+ return sessionSpaceId;
951
+ }
957
952
  }
958
- return (-offset - 1);
953
+ return undefined;
959
954
  }
960
955
  getClusterForFinalId(finalId) {
961
956
  const possibleCluster = this.finalIdToCluster.getPairOrNextLower(finalId);
@@ -986,6 +981,9 @@ class IdCompressor {
986
981
  if (!(0, Common_1.compareMaps)(this.sessions, other.sessions, (a, b) => IdCompressor.sessionDataEqual(a, b, true, compareLocalState))) {
987
982
  return false;
988
983
  }
984
+ if (!this.sessionIdNormalizer.equals(other.sessionIdNormalizer, (a, b) => IdCompressor.idClustersEqual(a, b, false, compareLocalState))) {
985
+ return false;
986
+ }
989
987
  }
990
988
  else {
991
989
  for (const [keyA, valueA] of this.sessions) {
@@ -1176,6 +1174,7 @@ class IdCompressor {
1176
1174
  localIdCount: this.localIdCount,
1177
1175
  overrides: [...this.localOverrides.entries()].map((entry) => [...entry]),
1178
1176
  lastTakenLocalId: this.lastTakenLocalId,
1177
+ sessionNormalizer: this.sessionIdNormalizer.serialize(),
1179
1178
  };
1180
1179
  }
1181
1180
  return serializedWithSession;
@@ -1249,12 +1248,6 @@ class IdCompressor {
1249
1248
  };
1250
1249
  const lastFinalizedNormalized = lastFinalizedLocalId !== null && lastFinalizedLocalId !== void 0 ? lastFinalizedLocalId : 0;
1251
1250
  const clusterBase = compressor.nextClusterBaseFinalId;
1252
- if (serializedLocalState !== undefined && sessionId === compressor.localSessionId) {
1253
- compressor.localIdToCluster.append((lastFinalizedNormalized - 1), [
1254
- clusterBase,
1255
- cluster,
1256
- ]);
1257
- }
1258
1251
  session.lastFinalizedLocalId = (lastFinalizedNormalized - count);
1259
1252
  session.currentClusterDetails = { clusterBase, cluster };
1260
1253
  compressor.nextClusterBaseFinalId = (compressor.nextClusterBaseFinalId + capacity);
@@ -1304,6 +1297,13 @@ class IdCompressor {
1304
1297
  }
1305
1298
  }
1306
1299
  }
1300
+ if (serializedLocalState !== undefined) {
1301
+ compressor.sessionIdNormalizer = SessionIdNormalizer_1.SessionIdNormalizer.deserialize(serializedLocalState.sessionNormalizer, (finalId) => {
1302
+ var _a;
1303
+ const [_, cluster] = (_a = compressor.finalIdToCluster.getPairOrNextLower(finalId)) !== null && _a !== void 0 ? _a : (0, Common_1.fail)('Final in serialized normalizer was never created.');
1304
+ return cluster;
1305
+ });
1306
+ }
1307
1307
  (0, Common_1.assert)(compressor.localSession.lastFinalizedLocalId === undefined ||
1308
1308
  compressor.localIdCount >= -compressor.localSession.lastFinalizedLocalId);
1309
1309
  return compressor;