@fluid-experimental/tree 0.59.3003 → 0.59.4000-71128

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 (86) hide show
  1. package/dist/Forest.js +1 -1
  2. package/dist/Forest.js.map +1 -1
  3. package/dist/SharedTree.d.ts +89 -30
  4. package/dist/SharedTree.d.ts.map +1 -1
  5. package/dist/SharedTree.js +93 -58
  6. package/dist/SharedTree.js.map +1 -1
  7. package/dist/SharedTreeEncoder.d.ts +1 -1
  8. package/dist/SharedTreeEncoder.d.ts.map +1 -1
  9. package/dist/SharedTreeEncoder.js.map +1 -1
  10. package/dist/id-compressor/IdCompressor.d.ts +19 -45
  11. package/dist/id-compressor/IdCompressor.d.ts.map +1 -1
  12. package/dist/id-compressor/IdCompressor.js +151 -151
  13. package/dist/id-compressor/IdCompressor.js.map +1 -1
  14. package/dist/id-compressor/SessionIdNormalizer.d.ts +1 -16
  15. package/dist/id-compressor/SessionIdNormalizer.d.ts.map +1 -1
  16. package/dist/id-compressor/SessionIdNormalizer.js +23 -21
  17. package/dist/id-compressor/SessionIdNormalizer.js.map +1 -1
  18. package/dist/id-compressor/persisted-types/0.0.1.d.ts +23 -4
  19. package/dist/id-compressor/persisted-types/0.0.1.d.ts.map +1 -1
  20. package/dist/id-compressor/persisted-types/0.0.1.js.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js.map +1 -1
  24. package/lib/Forest.js +1 -1
  25. package/lib/Forest.js.map +1 -1
  26. package/lib/SharedTree.d.ts +89 -30
  27. package/lib/SharedTree.d.ts.map +1 -1
  28. package/lib/SharedTree.js +94 -59
  29. package/lib/SharedTree.js.map +1 -1
  30. package/lib/SharedTreeEncoder.d.ts +1 -1
  31. package/lib/SharedTreeEncoder.d.ts.map +1 -1
  32. package/lib/SharedTreeEncoder.js.map +1 -1
  33. package/lib/id-compressor/IdCompressor.d.ts +19 -45
  34. package/lib/id-compressor/IdCompressor.d.ts.map +1 -1
  35. package/lib/id-compressor/IdCompressor.js +152 -152
  36. package/lib/id-compressor/IdCompressor.js.map +1 -1
  37. package/lib/id-compressor/SessionIdNormalizer.d.ts +1 -16
  38. package/lib/id-compressor/SessionIdNormalizer.d.ts.map +1 -1
  39. package/lib/id-compressor/SessionIdNormalizer.js +23 -21
  40. package/lib/id-compressor/SessionIdNormalizer.js.map +1 -1
  41. package/lib/id-compressor/persisted-types/0.0.1.d.ts +23 -4
  42. package/lib/id-compressor/persisted-types/0.0.1.d.ts.map +1 -1
  43. package/lib/id-compressor/persisted-types/0.0.1.js.map +1 -1
  44. package/lib/index.d.ts +1 -1
  45. package/lib/index.d.ts.map +1 -1
  46. package/lib/index.js.map +1 -1
  47. package/lib/test/IdCompressor.perf.tests.js +47 -67
  48. package/lib/test/IdCompressor.perf.tests.js.map +1 -1
  49. package/lib/test/IdCompressor.tests.js +196 -101
  50. package/lib/test/IdCompressor.tests.js.map +1 -1
  51. package/lib/test/Summary.tests.d.ts +0 -1
  52. package/lib/test/Summary.tests.d.ts.map +1 -1
  53. package/lib/test/Summary.tests.js +0 -3
  54. package/lib/test/Summary.tests.js.map +1 -1
  55. package/lib/test/Virtualization.tests.js +0 -4
  56. package/lib/test/Virtualization.tests.js.map +1 -1
  57. package/lib/test/fuzz/SharedTreeFuzzTests.d.ts.map +1 -1
  58. package/lib/test/fuzz/SharedTreeFuzzTests.js +3 -1
  59. package/lib/test/fuzz/SharedTreeFuzzTests.js.map +1 -1
  60. package/lib/test/utilities/IdCompressorTestUtilities.d.ts +14 -7
  61. package/lib/test/utilities/IdCompressorTestUtilities.d.ts.map +1 -1
  62. package/lib/test/utilities/IdCompressorTestUtilities.js +40 -20
  63. package/lib/test/utilities/IdCompressorTestUtilities.js.map +1 -1
  64. package/lib/test/utilities/SharedTreeTests.d.ts.map +1 -1
  65. package/lib/test/utilities/SharedTreeTests.js +27 -51
  66. package/lib/test/utilities/SharedTreeTests.js.map +1 -1
  67. package/lib/test/utilities/SharedTreeVersioningTests.d.ts.map +1 -1
  68. package/lib/test/utilities/SharedTreeVersioningTests.js +19 -19
  69. package/lib/test/utilities/SharedTreeVersioningTests.js.map +1 -1
  70. package/lib/test/utilities/SummaryLoadPerfTests.js +1 -1
  71. package/lib/test/utilities/SummaryLoadPerfTests.js.map +1 -1
  72. package/lib/test/utilities/TestCommon.d.ts +4 -0
  73. package/lib/test/utilities/TestCommon.d.ts.map +1 -1
  74. package/lib/test/utilities/TestCommon.js +6 -0
  75. package/lib/test/utilities/TestCommon.js.map +1 -1
  76. package/lib/test/utilities/TestUtilities.d.ts.map +1 -1
  77. package/lib/test/utilities/TestUtilities.js +4 -6
  78. package/lib/test/utilities/TestUtilities.js.map +1 -1
  79. package/package.json +24 -19
  80. package/src/Forest.ts +1 -1
  81. package/src/SharedTree.ts +195 -46
  82. package/src/SharedTreeEncoder.ts +1 -1
  83. package/src/id-compressor/IdCompressor.ts +171 -198
  84. package/src/id-compressor/SessionIdNormalizer.ts +29 -41
  85. package/src/id-compressor/persisted-types/0.0.1.ts +25 -4
  86. package/src/index.ts +4 -0
@@ -6,9 +6,10 @@
6
6
  import BTree from 'sorted-btree';
7
7
  import { assert, hasLength, assertNotUndefined, compareFiniteNumbers, compareFiniteNumbersReversed, compareMaps, compareStrings, fail, getOrCreate, setPropertyIfDefined, } from '../Common';
8
8
  import { assertIsStableId, assertIsUuidString, isStableId } from '../UuidUtilities';
9
- import { AppendOnlyDoublySortedMap, AppendOnlySortedMap } from './AppendOnlySortedMap';
9
+ import { AppendOnlySortedMap } from './AppendOnlySortedMap';
10
10
  import { getIds } from './IdRange';
11
11
  import { numericUuidEquals, getPositiveDelta, incrementUuid, numericUuidFromStableId, stableIdFromNumericUuid, ensureSessionUuid, } from './NumericUuid';
12
+ import { SessionIdNormalizer } from './SessionIdNormalizer';
12
13
  /**
13
14
  * Roughly equates to a minimum of 1M sessions before we start allocating 64 bit IDs.
14
15
  * This value must *NOT* change without careful consideration to compatibility.
@@ -136,11 +137,21 @@ export class IdCompressor {
136
137
  */
137
138
  this.localOverrides = new AppendOnlySortedMap(compareFiniteNumbersReversed);
138
139
  /**
139
- * Maps local IDs to the cluster they belong to (if any). This can be used to efficiently convert a local ID to a
140
- * final ID by finding an entry \<= a given local ID (to find the cluster it is associated with) and checking
141
- * it against `numFinalizedLocalIds`.
140
+ * Maps local IDs to the final ID they are associated with (if any), and maps final IDs to the corresponding local ID (if any).
141
+ * This is used to efficiently compute normalization. This map can be thought of as mapping ranges of "optimistic uncertainty"
142
+ * (local IDs) to the result of consensus (reserved ranges of final IDs, a.k.a. clusters). Any given range of local IDs
143
+ * does not necessarily span an entire cluster, as some session-space IDs may be allocated *after* a cluster has been allocated
144
+ * 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.
145
+ * However, there is always a range of local IDs with size \>= 1 associated with the beginning of every cluster, as clusters are only
146
+ * created *after* they are needed and thus there is some period of uncertainty after local IDs have been handed out but before the
147
+ * range containing them has been finalized. There may also be ranges of local IDs that do not start at the beginning of a
148
+ * cluster; this happens when a cluster is expanded instead of allocating a new one.
149
+ * Additionally, session space IDs associated with an override string will also always be local IDs, because there is uncertainty as
150
+ * to whether another client simultaneously allocated the same override and could get sequenced first (a.k.a. unification) and its
151
+ * final ID would be associated with that override.
152
+ * See `SessionIdNormalizer` for more.
142
153
  */
143
- this.localIdToCluster = new AppendOnlyDoublySortedMap(compareFiniteNumbersReversed, (value) => value[0], compareFiniteNumbers);
154
+ this.sessionIdNormalizer = new SessionIdNormalizer();
144
155
  /**
145
156
  * Contains entries for cluster base UUIDs and override strings (both local and final).
146
157
  * As a performance optimization, entries for finalized strings also include the containing cluster object.
@@ -196,12 +207,6 @@ export class IdCompressor {
196
207
  get attributionId() {
197
208
  return this.localSession.attributionId;
198
209
  }
199
- /**
200
- * Helper comparator for searching append-only sorted maps.
201
- */
202
- static overrideComparator(search, element) {
203
- return compareFiniteNumbers(search, element[0]);
204
- }
205
210
  /**
206
211
  * Creates a session object for the supplied ID.
207
212
  * Must only be called once per ID.
@@ -239,77 +244,28 @@ export class IdCompressor {
239
244
  /**
240
245
  * Returns an iterable of all IDs created by this compressor.
241
246
  */
242
- *getAllIdsFromLocalSession() {
243
- // TODO: this will change when final IDs are returned eagerly
244
- for (let i = 1; i <= this.localIdCount; i++) {
245
- yield -i;
246
- }
247
+ getAllIdsFromLocalSession() {
248
+ return this.sessionIdNormalizer[Symbol.iterator]();
247
249
  }
248
250
  /**
249
251
  * Returns the attribution ID associated with the compressor that created the ID
250
252
  */
251
253
  attributeId(id) {
252
- var _a;
253
254
  const opSpaceNormalizedId = this.normalizeToOpSpace(id);
254
255
  if (isLocalId(opSpaceNormalizedId)) {
255
256
  return this.attributionId;
256
257
  }
257
- const [_, cluster] = (_a = this.getClusterForFinalId(opSpaceNormalizedId)) !== null && _a !== void 0 ? _a : fail('Cluster does not exist for final ID');
258
- return cluster.session.attributionId;
259
- }
260
- /**
261
- * Provides the session-space IDs corresponding to a range of IDs.
262
- * See `IdRange` for more details.
263
- */
264
- getIdsFromRange(rangeDescriptor, sessionId) {
265
- var _a, _b, _c;
266
- const { first, count } = rangeDescriptor;
267
- if (sessionId === this.localSessionId) {
268
- return {
269
- length: count,
270
- get: (index) => {
271
- if (index < 0 || index >= count) {
272
- fail('Index out of bounds of range.');
273
- }
274
- return (first - index);
275
- },
276
- };
277
- }
278
- else {
279
- const session = (_a = this.sessions.get(sessionId)) !== null && _a !== void 0 ? _a : fail('Unknown session, range may not be finalized.');
280
- const firstNumericUuid = incrementUuid(session.sessionUuid, -first - 1);
281
- const firstFinal = (_b = this.compressNumericUuid(firstNumericUuid)) !== null && _b !== void 0 ? _b : fail('Remote range must be finalized before getting IDs.');
282
- assert(isFinalId(firstFinal), 'ID from a remote session ID must have final form, as overrides are impossible by definition.');
283
- const [baseFinalId, cluster] = (_c = this.getClusterForFinalId(firstFinal)) !== null && _c !== void 0 ? _c : fail();
284
- const numIdsRemainingInFirstCluster = cluster.capacity - (firstFinal - baseFinalId);
285
- let pivotFinal;
286
- if (count > numIdsRemainingInFirstCluster) {
287
- const compressedPivot = this.compressNumericUuid(incrementUuid(firstNumericUuid, numIdsRemainingInFirstCluster));
288
- // Looking up the actual cluster can be avoided, as it is guaranteed that at most one new cluster will be
289
- // created when finalizing a range (regardless of size) due to the expansion optimization.
290
- if (compressedPivot === undefined || isLocalId(compressedPivot)) {
291
- fail('ID from a remote session ID must have final form, as overrides are impossible by definition.');
292
- }
293
- else {
294
- pivotFinal = compressedPivot;
295
- }
258
+ const closestCluster = this.getClusterForFinalId(opSpaceNormalizedId);
259
+ if (closestCluster === undefined) {
260
+ if (this.sessionIdNormalizer.getCreationIndex(opSpaceNormalizedId) !== undefined) {
261
+ return this.attributionId;
262
+ }
263
+ else {
264
+ fail('Cluster does not exist for final ID');
296
265
  }
297
- return {
298
- length: count,
299
- get: (index) => {
300
- if (index < 0 || index >= count) {
301
- fail('Index out of bounds of range.');
302
- }
303
- if (index < numIdsRemainingInFirstCluster) {
304
- return (firstFinal + index);
305
- }
306
- else {
307
- return ((pivotFinal !== null && pivotFinal !== void 0 ? pivotFinal : fail('Pivot must exist if range spans clusters.')) +
308
- (index - numIdsRemainingInFirstCluster));
309
- }
310
- },
311
- };
312
266
  }
267
+ const [_, cluster] = closestCluster;
268
+ return cluster.session.attributionId;
313
269
  }
314
270
  /**
315
271
  * Returns a range of local IDs created by this session in a format for sending to the server for finalizing.
@@ -365,7 +321,7 @@ export class IdCompressor {
365
321
  * @param range - the range of session-local IDs to finalize.
366
322
  */
367
323
  finalizeCreationRange(range) {
368
- var _a, _b, _c;
324
+ var _a, _b, _c, _d;
369
325
  const { sessionId, attributionId } = range;
370
326
  const isLocal = sessionId === this.localSessionId;
371
327
  const session = (_a = this.sessions.get(sessionId)) !== null && _a !== void 0 ? _a : this.createSession(sessionId, attributionId);
@@ -379,16 +335,26 @@ export class IdCompressor {
379
335
  cluster: undefined,
380
336
  clusterBase: undefined,
381
337
  };
382
- const normalizedLastFinalized = (_b = session.lastFinalizedLocalId) !== null && _b !== void 0 ? _b : 0;
383
- const { first: newFirstFinalizedLocalId, last: newLastFinalizedLocalId } = ids;
384
- assert(newFirstFinalizedLocalId === normalizedLastFinalized - 1, 'Ranges finalized out of order.');
338
+ const currentClusterExists = currentCluster !== undefined && currentBaseFinalId !== undefined;
339
+ const normalizedLastFinalizedLocal = (_b = session.lastFinalizedLocalId) !== null && _b !== void 0 ? _b : 0;
340
+ const { first: newFirstFinalizedLocal, last: newLastFinalizedLocal } = ids;
341
+ assert(newFirstFinalizedLocal === normalizedLastFinalizedLocal - 1, 'Ranges finalized out of order.');
385
342
  // The total number of session-local IDs to finalize
386
- const finalizeCount = normalizedLastFinalized - newLastFinalizedLocalId;
343
+ const finalizeCount = normalizedLastFinalizedLocal - newLastFinalizedLocal;
387
344
  assert(finalizeCount >= 1, 'Cannot finalize an empty range.');
388
345
  let initialClusterCount = 0;
389
346
  let remainingCount = finalizeCount;
390
347
  let newBaseUuid;
391
- if (currentCluster !== undefined && currentBaseFinalId !== undefined) {
348
+ if (currentClusterExists) {
349
+ if (isLocal) {
350
+ const lastKnownFinal = (_c = this.sessionIdNormalizer.getLastFinalId()) !== null && _c !== void 0 ? _c : fail('Cluster exists but normalizer does not have an entry for it.');
351
+ const lastFinalInCluster = (currentBaseFinalId +
352
+ Math.min(currentCluster.count + finalizeCount, currentCluster.capacity) -
353
+ 1);
354
+ if (lastFinalInCluster > lastKnownFinal) {
355
+ this.sessionIdNormalizer.addFinalIds((lastKnownFinal + 1), lastFinalInCluster, currentCluster);
356
+ }
357
+ }
392
358
  initialClusterCount = currentCluster.count;
393
359
  const remainingCapacity = currentCluster.capacity - initialClusterCount;
394
360
  const overflow = remainingCount - remainingCapacity;
@@ -405,6 +371,21 @@ export class IdCompressor {
405
371
  this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId + expansionAmount);
406
372
  assert(this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER, 'The number of allocated final IDs must not exceed the JS maximum safe integer.');
407
373
  this.checkClusterForCollision(currentCluster);
374
+ if (isLocal) {
375
+ // Example with cluster size of 3:
376
+ // Ids generated so far: -1 1 2 -4 -5 <-- note positive numbers are eager finals
377
+ // Cluster: [ 0 1 2 ]
378
+ // ~ finalizing happens, causing expansion ~
379
+ // Cluster: [ 0 1 2 3 4 5 ]
380
+ // corresponding locals: -1 -4
381
+ // lastFinalizedLocalId^ ^newLastFinalizedLocalId = -6
382
+ // overflow = 2: ----
383
+ // localIdPivot^
384
+ // lastFinalizedFinal^
385
+ const lastFinalizedFinal = (currentBaseFinalId + currentCluster.count - 1);
386
+ const finalPivot = (lastFinalizedFinal - overflow + 1);
387
+ this.sessionIdNormalizer.addFinalIds(finalPivot, lastFinalizedFinal, currentCluster);
388
+ }
408
389
  }
409
390
  }
410
391
  else {
@@ -445,9 +426,10 @@ export class IdCompressor {
445
426
  session,
446
427
  };
447
428
  const usedCapacity = finalizeCount - remainingCount;
448
- localIdPivot = (newFirstFinalizedLocalId - usedCapacity);
429
+ localIdPivot = (newFirstFinalizedLocal - usedCapacity);
449
430
  if (isLocal) {
450
- this.localIdToCluster.append(localIdPivot, [newBaseFinalId, newCluster]);
431
+ const lastFinalizedFinal = (newBaseFinalId + newCluster.count - 1);
432
+ this.sessionIdNormalizer.addFinalIds(newBaseFinalId, lastFinalizedFinal, newCluster);
451
433
  }
452
434
  this.checkClusterForCollision(newCluster);
453
435
  this.clustersAndOverridesInversion.set(stableIdFromNumericUuid(newCluster.baseUuid), {
@@ -466,8 +448,8 @@ export class IdCompressor {
466
448
  const [overriddenLocal, override] = overrides[i];
467
449
  // Note: recall that local IDs are negative
468
450
  assert(i === 0 || overriddenLocal < overrides[i - 1][0], 'Override IDs must be in sorted order.');
469
- assert(overriddenLocal < normalizedLastFinalized, 'Ranges finalized out of order.');
470
- assert(overriddenLocal >= newLastFinalizedLocalId, 'Malformed range: override ID ahead of range start.');
451
+ assert(overriddenLocal < normalizedLastFinalizedLocal, 'Ranges finalized out of order.');
452
+ assert(overriddenLocal >= newLastFinalizedLocal, 'Malformed range: override ID ahead of range start.');
471
453
  let cluster;
472
454
  let overriddenFinal;
473
455
  if (localIdPivot !== undefined && overriddenLocal <= localIdPivot) {
@@ -482,10 +464,10 @@ export class IdCompressor {
482
464
  cluster = currentCluster;
483
465
  overriddenFinal = (currentBaseFinalId +
484
466
  initialClusterCount +
485
- (normalizedLastFinalized - overriddenLocal) -
467
+ (normalizedLastFinalizedLocal - overriddenLocal) -
486
468
  1);
487
469
  }
488
- (_c = cluster.overrides) !== null && _c !== void 0 ? _c : (cluster.overrides = new Map());
470
+ (_d = cluster.overrides) !== null && _d !== void 0 ? _d : (cluster.overrides = new Map());
489
471
  const inversionKey = IdCompressor.createInversionKey(override);
490
472
  const existingIds = this.getExistingIdsForNewOverride(inversionKey, true);
491
473
  let overrideForCluster;
@@ -551,7 +533,7 @@ export class IdCompressor {
551
533
  }
552
534
  }
553
535
  }
554
- session.lastFinalizedLocalId = newLastFinalizedLocalId;
536
+ session.lastFinalizedLocalId = newLastFinalizedLocal;
555
537
  }
556
538
  checkClusterForCollision(cluster) {
557
539
  const maxClusterUuid = incrementUuid(cluster.baseUuid, cluster.capacity - 1);
@@ -631,9 +613,9 @@ export class IdCompressor {
631
613
  }
632
614
  const override = (_a = numericOverride !== null && numericOverride !== void 0 ? numericOverride : stableOverride) !== null && _a !== void 0 ? _a : (IdCompressor.isStableInversionKey(inversionKey) ? inversionKey : undefined);
633
615
  if (override !== undefined) {
634
- const localId = this.getLocalIdForStableId(override);
635
- if (localId !== undefined) {
636
- return localId;
616
+ const sessionSpaceId = this.getCompressedIdForStableId(override);
617
+ if (sessionSpaceId !== undefined) {
618
+ return sessionSpaceId;
637
619
  }
638
620
  }
639
621
  return undefined;
@@ -675,41 +657,50 @@ export class IdCompressor {
675
657
  * @returns an existing ID if one already exists for `override`, and a new local ID otherwise. The returned ID is in session space.
676
658
  */
677
659
  generateCompressedId(override) {
678
- // If any ID exists for this override (locally or remotely allocated), return it (after ensuring it is in session-space).
660
+ let overrideInversionKey;
679
661
  if (override !== undefined) {
680
- const inversionKey = IdCompressor.createInversionKey(override);
681
- const existingIds = this.getExistingIdsForNewOverride(inversionKey, false);
662
+ overrideInversionKey = IdCompressor.createInversionKey(override);
663
+ const existingIds = this.getExistingIdsForNewOverride(overrideInversionKey, false);
682
664
  if (existingIds !== undefined) {
683
665
  return typeof existingIds === 'number' ? existingIds : existingIds[0];
684
666
  }
685
- else {
686
- const newLocalId = this.generateNextLocalId();
687
- this.localOverrides.append(newLocalId, override);
688
- // Since the local ID was just created, it is in both session and op space
689
- const compressionMapping = newLocalId;
690
- this.clustersAndOverridesInversion.set(inversionKey, compressionMapping);
691
- return newLocalId;
667
+ }
668
+ // Bump local counter regardless, then attempt to optimistically return a final ID.
669
+ // If the local session has reserved a cluster range via consensus, it is safe to hand out final IDs prior to
670
+ // finalizing the range that includes these locals.
671
+ const newLocalId = -++this.localIdCount;
672
+ const { currentClusterDetails } = this.localSession;
673
+ const { sessionIdNormalizer } = this;
674
+ let eagerFinalId;
675
+ let cluster;
676
+ if (currentClusterDetails !== undefined) {
677
+ const { clusterBase } = currentClusterDetails;
678
+ cluster = currentClusterDetails.cluster;
679
+ const lastFinalKnown = sessionIdNormalizer.getLastFinalId();
680
+ if (lastFinalKnown !== undefined && lastFinalKnown - clusterBase + 1 < cluster.capacity) {
681
+ eagerFinalId = (lastFinalKnown + 1);
692
682
  }
693
683
  }
684
+ if (overrideInversionKey !== undefined) {
685
+ const registeredLocal = sessionIdNormalizer.addLocalId();
686
+ assert(registeredLocal === newLocalId, 'TODO');
687
+ if (eagerFinalId !== undefined) {
688
+ sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster !== null && cluster !== void 0 ? cluster : fail());
689
+ }
690
+ this.localOverrides.append(newLocalId, override !== null && override !== void 0 ? override : fail());
691
+ // Since the local ID was just created, it is in both session and op space
692
+ const compressionMapping = newLocalId;
693
+ this.clustersAndOverridesInversion.set(overrideInversionKey, compressionMapping);
694
+ }
695
+ else if (eagerFinalId !== undefined) {
696
+ sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster !== null && cluster !== void 0 ? cluster : fail());
697
+ return eagerFinalId;
698
+ }
694
699
  else {
695
- return this.generateNextLocalId();
700
+ const registeredLocal = sessionIdNormalizer.addLocalId();
701
+ assert(registeredLocal === newLocalId, 'TODO');
696
702
  }
697
- }
698
- /**
699
- * Generates a range of compressed IDs.
700
- * This should ONLY be called to generate IDs for local operations.
701
- * @param count - the number of IDs to generate, must be \> 0.
702
- * @returns a persistable descriptor of the ID range.
703
- */
704
- generateCompressedIdRange(count) {
705
- assert(count > 0, 'Must generate a nonzero number of IDs.');
706
- assert(count <= Number.MAX_SAFE_INTEGER, 'The number of allocated local IDs must not exceed the JS maximum safe integer.');
707
- const first = this.generateNextLocalId();
708
- this.localIdCount += count - 1;
709
- return { first, count };
710
- }
711
- generateNextLocalId() {
712
- return -++this.localIdCount;
703
+ return newLocalId;
713
704
  }
714
705
  /**
715
706
  * Decompresses a previously compressed ID into a UUID or override string.
@@ -730,6 +721,11 @@ export class IdCompressor {
730
721
  if (isFinalId(id)) {
731
722
  const possibleCluster = this.getClusterForFinalId(id);
732
723
  if (possibleCluster === undefined) {
724
+ // It may be an unfinalized eager final ID, so check with normalizer to get the offset from the session UUID
725
+ const creationIndex = this.sessionIdNormalizer.getCreationIndex(id);
726
+ if (creationIndex !== undefined) {
727
+ return stableIdFromNumericUuid(this.localSession.sessionUuid, creationIndex);
728
+ }
733
729
  return undefined;
734
730
  }
735
731
  else {
@@ -797,9 +793,6 @@ export class IdCompressor {
797
793
  return compressionMapping;
798
794
  }
799
795
  else {
800
- const cluster = compressionMapping.cluster;
801
- assert(IdCompressor.tryGetOverride(cluster, compressionMapping.originalOverridingFinal) !==
802
- undefined, 'No override for cluster marked as having one.');
803
796
  return ((_a = compressionMapping.associatedLocalId) !== null && _a !== void 0 ? _a : compressionMapping.originalOverridingFinal);
804
797
  }
805
798
  }
@@ -828,9 +821,9 @@ export class IdCompressor {
828
821
  }
829
822
  if (isStable) {
830
823
  // May have already computed the numeric UUID, so avoid recomputing if possible
831
- const localId = this.getLocalIdForStableId(numericUuid !== null && numericUuid !== void 0 ? numericUuid : inversionKey);
832
- if (localId !== undefined) {
833
- return localId;
824
+ const sessionSpaceId = this.getCompressedIdForStableId(numericUuid !== null && numericUuid !== void 0 ? numericUuid : inversionKey);
825
+ if (sessionSpaceId !== undefined) {
826
+ return sessionSpaceId;
834
827
  }
835
828
  }
836
829
  return undefined;
@@ -849,9 +842,14 @@ export class IdCompressor {
849
842
  if (-id > this.localIdCount) {
850
843
  fail('Supplied local ID was not created by this compressor.');
851
844
  }
852
- // Check if this local ID has not been finalized yet
845
+ // Check if this local ID has not been finalized yet.
846
+ // Comparing lastFinalizedLocalId is a safe check for eager final IDs because the local IDs corresponding to them
847
+ // are never handed out to a consumer, and thus could not be passed into this method.
853
848
  const { lastFinalizedLocalId } = this.localSession;
854
849
  if (lastFinalizedLocalId === undefined || id < lastFinalizedLocalId) {
850
+ // Eager final IDs do not have overrides in the cluster until finalizing
851
+ // This means that using the normalizer to get the final/cluster associated would succeed but would not have the override,
852
+ // so checking localOverrides first is necessary.
855
853
  const override = this.localOverrides.get(id);
856
854
  if (override !== undefined) {
857
855
  const inversionKey = IdCompressor.createInversionKey(override);
@@ -864,8 +862,7 @@ export class IdCompressor {
864
862
  }
865
863
  return id;
866
864
  }
867
- const [localBase, [finalBase, cluster]] = (_b = this.localIdToCluster.getPairOrNextLower(id)) !== null && _b !== void 0 ? _b : fail('Locally created cluster should be added to the map when allocated');
868
- const correspondingFinal = (finalBase + (localBase - id));
865
+ const [correspondingFinal, cluster] = (_b = this.sessionIdNormalizer.getFinalId(id)) !== null && _b !== void 0 ? _b : fail('Locally created cluster should be added to the map when allocated');
869
866
  if (cluster.overrides) {
870
867
  const override = cluster.overrides.get(correspondingFinal);
871
868
  if (typeof override === 'object' && override.originalOverridingFinal !== undefined) {
@@ -875,11 +872,10 @@ export class IdCompressor {
875
872
  }
876
873
  return correspondingFinal;
877
874
  }
878
- normalizeToSessionSpace(id, originSessionId) {
879
- var _a, _b, _c;
880
- const isLocalSession = originSessionId === this.localSessionId;
875
+ normalizeToSessionSpace(id, sessionIdIfLocal) {
876
+ var _a, _b, _c, _d;
881
877
  if (isLocalId(id)) {
882
- if (isLocalSession) {
878
+ if (sessionIdIfLocal === undefined || sessionIdIfLocal === this.localSessionId) {
883
879
  const localIndex = -id;
884
880
  if (localIndex > this.localIdCount) {
885
881
  fail('Supplied local ID was not created by this compressor.');
@@ -887,27 +883,20 @@ export class IdCompressor {
887
883
  return id;
888
884
  }
889
885
  else {
890
- const session = this.sessions.get(originSessionId !== null && originSessionId !== void 0 ? originSessionId : fail());
891
- if (session === undefined) {
892
- fail('No IDs have ever been finalized by the supplied session.');
893
- }
886
+ const session = (_a = this.sessions.get(sessionIdIfLocal)) !== null && _a !== void 0 ? _a : fail('No IDs have ever been finalized by the supplied session.');
894
887
  const localCount = -id;
895
888
  const numericUuid = incrementUuid(session.sessionUuid, localCount - 1);
896
- return (_a = this.compressNumericUuid(numericUuid)) !== null && _a !== void 0 ? _a : fail('ID is not known to this compressor.');
889
+ return (_b = this.compressNumericUuid(numericUuid)) !== null && _b !== void 0 ? _b : fail('ID is not known to this compressor.');
897
890
  }
898
891
  }
899
- const closestResult = this.localIdToCluster.getPairOrNextLowerByValue(id);
900
- if (closestResult !== undefined) {
901
- const [localBase, [finalBase, cluster]] = closestResult;
902
- const indexInCluster = id - finalBase;
903
- if (indexInCluster < cluster.count) {
904
- return (localBase - indexInCluster);
905
- }
892
+ const normalizedId = this.sessionIdNormalizer.getSessionSpaceId(id);
893
+ if (normalizedId !== undefined) {
894
+ return normalizedId;
906
895
  }
907
896
  // Check for a unified override finalized first by another session but to which the local session
908
897
  // still has an associated local ID.
909
- const [_, cluster] = (_b = this.getClusterForFinalId(id)) !== null && _b !== void 0 ? _b : fail('Supplied final ID was not finalized by this compressor.');
910
- const override = (_c = cluster.overrides) === null || _c === void 0 ? void 0 : _c.get(id);
898
+ const [_, cluster] = (_c = this.getClusterForFinalId(id)) !== null && _c !== void 0 ? _c : fail('Supplied final ID was not finalized by this compressor.');
899
+ const override = (_d = cluster.overrides) === null || _d === void 0 ? void 0 : _d.get(id);
911
900
  if (typeof override === 'object' && override.associatedLocalId !== undefined) {
912
901
  return override.associatedLocalId;
913
902
  }
@@ -941,13 +930,19 @@ export class IdCompressor {
941
930
  }
942
931
  return sessionSpaceId;
943
932
  }
944
- getLocalIdForStableId(stableId) {
933
+ /**
934
+ * Returns a compressed ID for the supplied stable ID if it was created by the local session, and undefined otherwise.
935
+ */
936
+ getCompressedIdForStableId(stableId) {
945
937
  const numericUuid = typeof stableId === 'string' ? numericUuidFromStableId(stableId) : stableId;
946
- const offset = getPositiveDelta(numericUuid, this.localSession.sessionUuid, this.localIdCount - 1);
947
- if (offset === undefined) {
948
- return undefined;
938
+ const creationIndex = getPositiveDelta(numericUuid, this.localSession.sessionUuid, this.localIdCount - 1);
939
+ if (creationIndex !== undefined) {
940
+ const sessionSpaceId = this.sessionIdNormalizer.getIdByCreationIndex(creationIndex);
941
+ if (sessionSpaceId !== undefined) {
942
+ return sessionSpaceId;
943
+ }
949
944
  }
950
- return (-offset - 1);
945
+ return undefined;
951
946
  }
952
947
  getClusterForFinalId(finalId) {
953
948
  const possibleCluster = this.finalIdToCluster.getPairOrNextLower(finalId);
@@ -978,6 +973,9 @@ export class IdCompressor {
978
973
  if (!compareMaps(this.sessions, other.sessions, (a, b) => IdCompressor.sessionDataEqual(a, b, true, compareLocalState))) {
979
974
  return false;
980
975
  }
976
+ if (!this.sessionIdNormalizer.equals(other.sessionIdNormalizer, (a, b) => IdCompressor.idClustersEqual(a, b, false, compareLocalState))) {
977
+ return false;
978
+ }
981
979
  }
982
980
  else {
983
981
  for (const [keyA, valueA] of this.sessions) {
@@ -1168,6 +1166,7 @@ export class IdCompressor {
1168
1166
  localIdCount: this.localIdCount,
1169
1167
  overrides: [...this.localOverrides.entries()].map((entry) => [...entry]),
1170
1168
  lastTakenLocalId: this.lastTakenLocalId,
1169
+ sessionNormalizer: this.sessionIdNormalizer.serialize(),
1171
1170
  };
1172
1171
  }
1173
1172
  return serializedWithSession;
@@ -1241,12 +1240,6 @@ export class IdCompressor {
1241
1240
  };
1242
1241
  const lastFinalizedNormalized = lastFinalizedLocalId !== null && lastFinalizedLocalId !== void 0 ? lastFinalizedLocalId : 0;
1243
1242
  const clusterBase = compressor.nextClusterBaseFinalId;
1244
- if (serializedLocalState !== undefined && sessionId === compressor.localSessionId) {
1245
- compressor.localIdToCluster.append((lastFinalizedNormalized - 1), [
1246
- clusterBase,
1247
- cluster,
1248
- ]);
1249
- }
1250
1243
  session.lastFinalizedLocalId = (lastFinalizedNormalized - count);
1251
1244
  session.currentClusterDetails = { clusterBase, cluster };
1252
1245
  compressor.nextClusterBaseFinalId = (compressor.nextClusterBaseFinalId + capacity);
@@ -1296,6 +1289,13 @@ export class IdCompressor {
1296
1289
  }
1297
1290
  }
1298
1291
  }
1292
+ if (serializedLocalState !== undefined) {
1293
+ compressor.sessionIdNormalizer = SessionIdNormalizer.deserialize(serializedLocalState.sessionNormalizer, (finalId) => {
1294
+ var _a;
1295
+ const [_, cluster] = (_a = compressor.finalIdToCluster.getPairOrNextLower(finalId)) !== null && _a !== void 0 ? _a : fail('Final in serialized normalizer was never created.');
1296
+ return cluster;
1297
+ });
1298
+ }
1299
1299
  assert(compressor.localSession.lastFinalizedLocalId === undefined ||
1300
1300
  compressor.localIdCount >= -compressor.localSession.lastFinalizedLocalId);
1301
1301
  return compressor;