@fluidframework/id-compressor 2.13.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.eslintrc.cjs +1 -4
  2. package/CHANGELOG.md +4 -0
  3. package/dist/appendOnlySortedMap.d.ts +37 -16
  4. package/dist/appendOnlySortedMap.d.ts.map +1 -1
  5. package/dist/appendOnlySortedMap.js +39 -21
  6. package/dist/appendOnlySortedMap.js.map +1 -1
  7. package/dist/finalSpace.d.ts +2 -2
  8. package/dist/finalSpace.d.ts.map +1 -1
  9. package/dist/finalSpace.js +5 -4
  10. package/dist/finalSpace.js.map +1 -1
  11. package/dist/idCompressor.d.ts.map +1 -1
  12. package/dist/idCompressor.js +18 -15
  13. package/dist/idCompressor.js.map +1 -1
  14. package/dist/identifiers.d.ts +1 -1
  15. package/dist/identifiers.js +1 -1
  16. package/dist/identifiers.js.map +1 -1
  17. package/dist/package.json +2 -1
  18. package/dist/packageVersion.d.ts +1 -1
  19. package/dist/packageVersion.js +1 -1
  20. package/dist/packageVersion.js.map +1 -1
  21. package/dist/sessions.js.map +1 -1
  22. package/dist/test/appendOnlySortedMap.perf.spec.js.map +1 -1
  23. package/dist/test/appendOnlySortedMap.spec.js.map +1 -1
  24. package/dist/test/idCompressor.perf.spec.js.map +1 -1
  25. package/dist/test/idCompressor.spec.js.map +1 -1
  26. package/dist/test/idCompressorTestUtilities.d.ts +39 -13
  27. package/dist/test/idCompressorTestUtilities.d.ts.map +1 -1
  28. package/dist/test/idCompressorTestUtilities.js +67 -56
  29. package/dist/test/idCompressorTestUtilities.js.map +1 -1
  30. package/dist/test/numericUuid.spec.js.map +1 -1
  31. package/dist/test/sessionSpaceNormalizer.spec.js.map +1 -1
  32. package/dist/test/snapshots/summary.spec.js.map +1 -1
  33. package/dist/test/testCommon.d.ts +5 -1
  34. package/dist/test/testCommon.d.ts.map +1 -1
  35. package/dist/test/testCommon.js +2 -0
  36. package/dist/test/testCommon.js.map +1 -1
  37. package/dist/utilities.d.ts.map +1 -1
  38. package/dist/utilities.js +11 -6
  39. package/dist/utilities.js.map +1 -1
  40. package/lib/appendOnlySortedMap.d.ts +37 -16
  41. package/lib/appendOnlySortedMap.d.ts.map +1 -1
  42. package/lib/appendOnlySortedMap.js +39 -21
  43. package/lib/appendOnlySortedMap.js.map +1 -1
  44. package/lib/finalSpace.d.ts +2 -2
  45. package/lib/finalSpace.d.ts.map +1 -1
  46. package/lib/finalSpace.js +5 -4
  47. package/lib/finalSpace.js.map +1 -1
  48. package/lib/idCompressor.d.ts.map +1 -1
  49. package/lib/idCompressor.js +18 -15
  50. package/lib/idCompressor.js.map +1 -1
  51. package/lib/identifiers.d.ts +1 -1
  52. package/lib/identifiers.js +1 -1
  53. package/lib/identifiers.js.map +1 -1
  54. package/lib/packageVersion.d.ts +1 -1
  55. package/lib/packageVersion.js +1 -1
  56. package/lib/packageVersion.js.map +1 -1
  57. package/lib/sessions.js.map +1 -1
  58. package/lib/test/appendOnlySortedMap.perf.spec.js.map +1 -1
  59. package/lib/test/appendOnlySortedMap.spec.js.map +1 -1
  60. package/lib/test/idCompressor.perf.spec.js.map +1 -1
  61. package/lib/test/idCompressor.spec.js.map +1 -1
  62. package/lib/test/idCompressorTestUtilities.d.ts +39 -13
  63. package/lib/test/idCompressorTestUtilities.d.ts.map +1 -1
  64. package/lib/test/idCompressorTestUtilities.js +42 -31
  65. package/lib/test/idCompressorTestUtilities.js.map +1 -1
  66. package/lib/test/numericUuid.spec.js.map +1 -1
  67. package/lib/test/sessionSpaceNormalizer.spec.js.map +1 -1
  68. package/lib/test/snapshots/summary.spec.js.map +1 -1
  69. package/lib/test/testCommon.d.ts +5 -1
  70. package/lib/test/testCommon.d.ts.map +1 -1
  71. package/lib/test/testCommon.js +2 -0
  72. package/lib/test/testCommon.js.map +1 -1
  73. package/lib/utilities.d.ts.map +1 -1
  74. package/lib/utilities.js +11 -6
  75. package/lib/utilities.js.map +1 -1
  76. package/package.json +8 -8
  77. package/src/appendOnlySortedMap.ts +39 -21
  78. package/src/finalSpace.ts +6 -5
  79. package/src/idCompressor.ts +22 -18
  80. package/src/identifiers.ts +1 -1
  81. package/src/packageVersion.ts +1 -1
  82. package/src/sessions.ts +2 -2
  83. package/src/test/idCompressorTestUtilities.ts +69 -40
  84. package/src/test/testCommon.ts +5 -1
  85. package/src/utilities.ts +12 -7
@@ -64,7 +64,7 @@ import {
64
64
  * The version of IdCompressor that is currently persisted.
65
65
  * This should not be changed without careful consideration to compatibility.
66
66
  */
67
- const currentWrittenVersion = 2.0;
67
+ const currentWrittenVersion = 2;
68
68
 
69
69
  function rangeFinalizationError(expectedStart: number, actualStart: number): LoggingError {
70
70
  return new LoggingError("Ranges finalized out of order", {
@@ -202,7 +202,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
202
202
  /**
203
203
  * {@inheritdoc IIdCompressorCore.beginGhostSession}
204
204
  */
205
- public beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void) {
205
+ public beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void): void {
206
206
  this.startGhostSession(ghostSessionId);
207
207
  try {
208
208
  ghostSessionCallback();
@@ -554,7 +554,8 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
554
554
  !this.ongoingGhostSession,
555
555
  0x8a9 /* IdCompressor should not be operated normally when in a ghost session */,
556
556
  );
557
- const { normalizer, finalSpace, sessions } = this;
557
+ const { normalizer, finalSpace, sessions, localGenCount, logger, nextRangeBaseGenCount } =
558
+ this;
558
559
  const sessionIndexMap = new Map<Session, number>();
559
560
  let sessionIndex = 0;
560
561
  for (const session of sessions.sessions()) {
@@ -568,7 +569,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
568
569
  ? 1 + // generated ID count
569
570
  1 + // next range base genCount
570
571
  1 + // count of normalizer pairs
571
- this.normalizer.idRanges.size * 2 // pairs
572
+ normalizer.idRanges.size * 2 // pairs
572
573
  : 0;
573
574
  // Layout size, in 8 byte increments
574
575
  const totalSize =
@@ -592,7 +593,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
592
593
  index = writeNumericUuid(serializedUint, index, session.sessionUuid);
593
594
  }
594
595
 
595
- finalSpace.clusters.forEach((cluster) => {
596
+ for (const cluster of finalSpace.clusters) {
596
597
  index = writeNumber(
597
598
  serializedFloat,
598
599
  index,
@@ -600,11 +601,11 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
600
601
  );
601
602
  index = writeNumber(serializedFloat, index, cluster.capacity);
602
603
  index = writeNumber(serializedFloat, index, cluster.count);
603
- });
604
+ }
604
605
 
605
606
  if (hasLocalState) {
606
- index = writeNumber(serializedFloat, index, this.localGenCount);
607
- index = writeNumber(serializedFloat, index, this.nextRangeBaseGenCount);
607
+ index = writeNumber(serializedFloat, index, localGenCount);
608
+ index = writeNumber(serializedFloat, index, nextRangeBaseGenCount);
608
609
  index = writeNumber(serializedFloat, index, normalizer.idRanges.size);
609
610
  for (const [leadingGenCount, count] of normalizer.idRanges.entries()) {
610
611
  index = writeNumber(serializedFloat, index, leadingGenCount);
@@ -613,7 +614,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
613
614
  }
614
615
 
615
616
  assert(index === totalSize, 0x75b /* Serialized size was incorrectly calculated. */);
616
- this.logger?.sendTelemetryEvent({
617
+ logger?.sendTelemetryEvent({
617
618
  eventName: "RuntimeIdCompressor:SerializedIdCompressorSize",
618
619
  size: serializedFloat.byteLength,
619
620
  clusterCount: finalSpace.clusters.length,
@@ -652,12 +653,15 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
652
653
  };
653
654
  const version = readNumber(index);
654
655
  switch (version) {
655
- case 1.0:
656
+ case 1: {
656
657
  throw new Error("IdCompressor version 1.0 is no longer supported.");
657
- case 2.0:
658
+ }
659
+ case 2: {
658
660
  return IdCompressor.deserialize2_0(index, sessionId, logger);
659
- default:
661
+ }
662
+ default: {
660
663
  throw new Error("Unknown IdCompressor serialized version.");
664
+ }
661
665
  }
662
666
  }
663
667
 
@@ -673,17 +677,17 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
673
677
  // Sessions
674
678
  let sessionOffset = 0;
675
679
  const sessions: [NumericUuid, Session][] = [];
676
- if (!hasLocalState) {
680
+ if (hasLocalState) {
681
+ assert(
682
+ sessionId === undefined,
683
+ 0x75e /* Local state should not exist in serialized form. */,
684
+ );
685
+ } else {
677
686
  // If !hasLocalState, there won't be a serialized local session ID so insert one at the beginning
678
687
  assert(sessionId !== undefined, 0x75d /* Local session ID is undefined. */);
679
688
  const localSessionNumeric = numericUuidFromStableId(sessionId);
680
689
  sessions.push([localSessionNumeric, new Session(localSessionNumeric)]);
681
690
  sessionOffset = 1;
682
- } else {
683
- assert(
684
- sessionId === undefined,
685
- 0x75e /* Local state should not exist in serialized form. */,
686
- );
687
691
  }
688
692
 
689
693
  for (let i = 0; i < sessionCount; i++) {
@@ -26,7 +26,7 @@ export type LocalCompressedId = number & {
26
26
  } & SessionSpaceCompressedId; // Same brand as CompressedId, as local IDs are always locally normalized
27
27
 
28
28
  /**
29
- * @returns true if the supplied ID is a final ID.
29
+ * Returns true if the supplied ID is a final ID.
30
30
  */
31
31
  export function isFinalId(
32
32
  id: SessionSpaceCompressedId | OpSpaceCompressedId,
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/id-compressor";
9
- export const pkgVersion = "2.13.0";
9
+ export const pkgVersion = "2.20.0";
package/src/sessions.ts CHANGED
@@ -131,7 +131,7 @@ export class Sessions {
131
131
  }
132
132
 
133
133
  public equals(other: Sessions, includeLocalState: boolean): boolean {
134
- const checkIsSubset = (sessionsA: Sessions, sessionsB: Sessions) => {
134
+ const checkIsSubset = (sessionsA: Sessions, sessionsB: Sessions): boolean => {
135
135
  const first = sessionsA.sessions().next();
136
136
  const firstSessionThis = first.done ? undefined : first.value;
137
137
  for (const [stableId, session] of sessionsA.sessionCache.entries()) {
@@ -296,7 +296,7 @@ export class Session {
296
296
 
297
297
  public equals(other: Session): boolean {
298
298
  for (const [index, value] of Object.entries(this.clusterChain)) {
299
- if (!clustersEqual(value, other.clusterChain[index])) {
299
+ if (!clustersEqual(value, other.clusterChain[index] as IdCluster)) {
300
300
  return false;
301
301
  }
302
302
  }
@@ -3,7 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { strict as assert } from "assert";
6
+ // eslint-disable-next-line import/no-nodejs-modules
7
+ import { strict as assert } from "node:assert";
7
8
 
8
9
  import {
9
10
  BaseFuzzTestState,
@@ -51,19 +52,25 @@ export interface ClosedMap<K, V> extends Omit<Map<K, V>, "delete" | "clear"> {
51
52
  get(key: K): V;
52
53
  }
53
54
 
54
- /** Identifies a compressor in a network */
55
+ /**
56
+ * Identifies a compressor in a network
57
+ */
55
58
  export enum Client {
56
59
  Client1 = "Client1",
57
60
  Client2 = "Client2",
58
61
  Client3 = "Client3",
59
62
  }
60
63
 
61
- /** Identifies categories of compressors */
64
+ /**
65
+ * Identifies categories of compressors
66
+ */
62
67
  export enum MetaClient {
63
68
  All = "All",
64
69
  }
65
70
 
66
- /** Identifies a compressor inside the network but outside the three specially tracked clients. */
71
+ /**
72
+ * Identifies a compressor inside the network but outside the three specially tracked clients.
73
+ */
67
74
  export enum OutsideClient {
68
75
  Remote = "Remote",
69
76
  }
@@ -75,7 +82,9 @@ export enum OutsideClient {
75
82
  export type OriginatingClient = Client | OutsideClient;
76
83
  export const OriginatingClient = { ...Client, ...OutsideClient };
77
84
 
78
- /** Identifies a compressor to which to send an operation */
85
+ /**
86
+ * Identifies a compressor to which to send an operation
87
+ */
79
88
  export type DestinationClient = Client | MetaClient;
80
89
  export const DestinationClient = { ...Client, ...MetaClient };
81
90
 
@@ -171,7 +180,9 @@ function makeSessionIds(): ClientMap<SessionId> {
171
180
  */
172
181
  export const sessionIds = makeSessionIds();
173
182
 
174
- /** Information about a generated ID in a network to be validated by tests */
183
+ /**
184
+ * Information about a generated ID in a network to be validated by tests
185
+ */
175
186
  export interface TestIdData {
176
187
  readonly id: SessionSpaceCompressedId;
177
188
  readonly originatingClient: OriginatingClient;
@@ -184,20 +195,30 @@ export interface TestIdData {
184
195
  * Not suitable for performance testing.
185
196
  */
186
197
  export class IdCompressorTestNetwork {
187
- /** The compressors used in this network */
198
+ /**
199
+ * The compressors used in this network
200
+ */
188
201
  private readonly compressors: ClientMap<IdCompressor>;
189
- /** The log of operations seen by the server so far. Append-only. */
202
+ /**
203
+ * The log of operations seen by the server so far. Append-only.
204
+ */
190
205
  private readonly serverOperations: [
191
206
  creationRange: IdCreationRange,
192
207
  opSpaceIds: OpSpaceCompressedId[],
193
208
  clientFrom: OriginatingClient,
194
209
  sessionIdFrom: SessionId,
195
210
  ][] = [];
196
- /** An index into `serverOperations` for each client which represents how many operations have been delivered to that client */
211
+ /**
212
+ * An index into `serverOperations` for each client which represents how many operations have been delivered to that client
213
+ */
197
214
  private readonly clientProgress: ClientMap<number>;
198
- /** All ids (local and sequenced) that a client has created or received, in order. */
215
+ /**
216
+ * All ids (local and sequenced) that a client has created or received, in order.
217
+ */
199
218
  private readonly idLogs: ClientMap<TestIdData[]>;
200
- /** All ids that a client has received from the server, in order. */
219
+ /**
220
+ * All ids that a client has received from the server, in order.
221
+ */
201
222
  private readonly sequencedIdLogs: ClientMap<TestIdData[]>;
202
223
 
203
224
  public constructor(public readonly initialClusterSize = 5) {
@@ -341,6 +362,7 @@ export class IdCompressorTestNetwork {
341
362
  ids: {
342
363
  firstGenCount: 1,
343
364
  count: numIds,
365
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
344
366
  requestedClusterSize: this.getCompressor(Client.Client1)[
345
367
  // eslint-disable-next-line @typescript-eslint/dot-notation
346
368
  "nextRequestedClusterSize"
@@ -391,9 +413,9 @@ export class IdCompressorTestNetwork {
391
413
  opIndexBound = this.serverOperations.length;
392
414
  } else {
393
415
  opIndexBound =
394
- opsToDeliver !== undefined
395
- ? this.clientProgress.get(clientTakingDelivery) + opsToDeliver
396
- : this.serverOperations.length;
416
+ opsToDeliver === undefined
417
+ ? this.serverOperations.length
418
+ : this.clientProgress.get(clientTakingDelivery) + opsToDeliver;
397
419
  }
398
420
  for (const [clientTo, compressorTo] of this.getTargetCompressors(clientTakingDelivery)) {
399
421
  for (let i = this.clientProgress.get(clientTo); i < opIndexBound; i++) {
@@ -452,38 +474,38 @@ export class IdCompressorTestNetwork {
452
474
  };
453
475
 
454
476
  // Ensure creation ranges for clients we track contain the correct local ID ranges
455
- this.serverOperations.forEach(([range, opSpaceIds, clientFrom]) => {
477
+ for (const [range, opSpaceIds, clientFrom] of this.serverOperations) {
456
478
  if (clientFrom !== OriginatingClient.Remote) {
457
479
  const localIdsInCreationRange = getLocalIdsInRange(range, opSpaceIds);
458
480
  let localCount = 0;
459
- opSpaceIds.forEach((id) => {
481
+ for (const id of opSpaceIds) {
460
482
  if (isLocalId(id)) {
461
483
  localCount++;
462
484
  assert(localIdsInCreationRange.has(id), "Local ID not in creation range");
463
485
  }
464
- });
486
+ }
465
487
  assert.strictEqual(
466
488
  localCount,
467
489
  localIdsInCreationRange.size,
468
490
  "Local ID count mismatch",
469
491
  );
470
492
  }
471
- });
493
+ }
472
494
 
473
495
  const undeliveredRanges = new Map<Client, IdCreationRange[]>();
474
- this.clientProgress.forEach((progress, client) => {
496
+ for (const [client, progress] of this.clientProgress.entries()) {
475
497
  const ranges = this.serverOperations
476
498
  .slice(progress)
477
499
  .filter((op) => op[2] === client)
478
500
  .map(([range]) => range);
479
501
  undeliveredRanges.set(client, ranges);
480
- });
481
- undeliveredRanges.forEach((ranges, client) => {
502
+ }
503
+ for (const [client, ranges] of undeliveredRanges.entries()) {
482
504
  const compressor = this.compressors.get(client);
483
505
  let firstGenCount: number | undefined;
484
506
  let totalCount = 0;
485
507
  const unionedLocalRanges = new SessionSpaceNormalizer();
486
- ranges.forEach((range) => {
508
+ for (const range of ranges) {
487
509
  assert(range.sessionId === compressor.localSessionId);
488
510
  if (range.ids !== undefined) {
489
511
  // initialize firstGenCount if not set
@@ -491,18 +513,21 @@ export class IdCompressorTestNetwork {
491
513
  firstGenCount = range.ids.firstGenCount;
492
514
  }
493
515
  totalCount += range.ids.count;
494
- range.ids.localIdRanges.forEach(([genCount, count]) => {
516
+ for (const [genCount, count] of range.ids.localIdRanges) {
495
517
  unionedLocalRanges.addLocalRange(genCount, count);
496
- });
518
+ }
497
519
  }
498
- });
520
+ }
499
521
 
500
522
  const retakenRange = compressor.takeUnfinalizedCreationRange();
501
- if (retakenRange.ids !== undefined) {
523
+ if (retakenRange.ids === undefined) {
524
+ assert.strictEqual(totalCount, 0);
525
+ assert.strictEqual(unionedLocalRanges.idRanges.size, 0);
526
+ } else {
502
527
  const retakenLocalIds = new SessionSpaceNormalizer();
503
- retakenRange.ids.localIdRanges.forEach(([genCount, count]) => {
528
+ for (const [genCount, count] of retakenRange.ids.localIdRanges) {
504
529
  retakenLocalIds.addLocalRange(genCount, count);
505
- });
530
+ }
506
531
  assert.strictEqual(
507
532
  retakenLocalIds.equals(unionedLocalRanges),
508
533
  true,
@@ -510,11 +535,8 @@ export class IdCompressorTestNetwork {
510
535
  );
511
536
  assert.strictEqual(retakenRange.ids.count, totalCount, "Count mismatch");
512
537
  assert.strictEqual(retakenRange.ids.firstGenCount, firstGenCount, "Count mismatch");
513
- } else {
514
- assert.strictEqual(totalCount, 0);
515
- assert.strictEqual(unionedLocalRanges.idRanges.size, 0);
516
538
  }
517
- });
539
+ }
518
540
 
519
541
  // First, ensure all clients each generated a unique ID for each of their own calls to generate.
520
542
  for (const [compressor, ids] of sequencedLogs) {
@@ -528,6 +550,7 @@ export class IdCompressorTestNetwork {
528
550
 
529
551
  const maxLogLength = sequencedLogs
530
552
  .map(([_, data]) => data.length)
553
+ // eslint-disable-next-line unicorn/no-array-reduce
531
554
  .reduce((p, n) => Math.max(p, n));
532
555
 
533
556
  function getNextLogWithEntryAt(logsIndex: number, entryIndex: number): number | undefined {
@@ -680,8 +703,8 @@ export function roundtrip(
680
703
  ] {
681
704
  // preserve the capacity request as this property is normally private and resets
682
705
  // to a default on construction (deserialization)
683
- // eslint-disable-next-line @typescript-eslint/dot-notation
684
- const capacity = compressor["nextRequestedClusterSize"];
706
+ // eslint-disable-next-line @typescript-eslint/dot-notation, @typescript-eslint/no-unsafe-assignment
707
+ const capacity: number = compressor["nextRequestedClusterSize"];
685
708
  if (withSession) {
686
709
  const serialized = compressor.serialize(withSession);
687
710
  const roundtripped = IdCompressor.deserialize(serialized);
@@ -732,10 +755,10 @@ export function mergeArrayMaps<K, V>(
732
755
  ): Pick<Map<K, V[]>, "get" | "set"> {
733
756
  for (const [key, value] of from.entries()) {
734
757
  const entry = to.get(key);
735
- if (entry !== undefined) {
736
- entry.push(...value);
737
- } else {
758
+ if (entry === undefined) {
738
759
  to.set(key, [...value]);
760
+ } else {
761
+ entry.push(...value);
739
762
  }
740
763
  }
741
764
  return to;
@@ -796,11 +819,17 @@ interface FuzzTestState extends BaseFuzzTestState {
796
819
  }
797
820
 
798
821
  export interface OperationGenerationConfig {
799
- /** maximum cluster size of the network. Default: 25 */
822
+ /**
823
+ * maximum cluster size of the network. Default: 25
824
+ */
800
825
  maxClusterSize?: number;
801
- /** Number of ops between validation ops. Default: 200 */
826
+ /**
827
+ * Number of ops between validation ops. Default: 200
828
+ */
802
829
  validateInterval?: number;
803
- /** Fraction of ID allocations that are from an outside client (not Client1/2/3). */
830
+ /**
831
+ * Fraction of ID allocations that are from an outside client (not Client1/2/3).
832
+ */
804
833
  outsideAllocationFraction?: number;
805
834
  }
806
835
 
@@ -44,6 +44,7 @@ export type LocalCompressedId = number & {
44
44
  } & SessionSpaceCompressedId; // Same brand as CompressedId, as local IDs are always locally normalized
45
45
 
46
46
  /**
47
+ * Returns true if the supplied ID is a final ID.
47
48
  * @returns true if the supplied ID is a final ID.
48
49
  */
49
50
  export function isFinalId(id: CompressedId): id is FinalCompressedId {
@@ -51,6 +52,7 @@ export function isFinalId(id: CompressedId): id is FinalCompressedId {
51
52
  }
52
53
 
53
54
  /**
55
+ * Returns true if the supplied ID is a local ID.
54
56
  * @returns true if the supplied ID is a local ID.
55
57
  */
56
58
  export function isLocalId(id: CompressedId): id is LocalCompressedId {
@@ -91,7 +93,9 @@ export function incrementStableId(stableId: StableId, offset: number): StableId
91
93
  return stableIdFromNumericUuid(offsetNumericUuid(numericUuidFromStableId(stableId), offset));
92
94
  }
93
95
 
94
- /** An immutable view of an `IdCompressor` */
96
+ /**
97
+ * An immutable view of an `IdCompressor`
98
+ */
95
99
  export type ReadonlyIdCompressor = Omit<
96
100
  IdCompressor,
97
101
  | "generateCompressedId"
package/src/utilities.ts CHANGED
@@ -10,7 +10,7 @@ import { v4 } from "uuid";
10
10
  import { LocalCompressedId, NumericUuid } from "./identifiers.js";
11
11
  import { SessionId, StableId } from "./types/index.js";
12
12
 
13
- const hexadecimalCharCodes = Array.from("09afAF").map((c) => c.charCodeAt(0)) as [
13
+ const hexadecimalCharCodes = [..."09afAF"].map((c) => c.codePointAt(0)) as [
14
14
  zero: number,
15
15
  nine: number,
16
16
  a: number,
@@ -76,17 +76,19 @@ export function isStableId(str: string): str is StableId {
76
76
  case 8:
77
77
  case 13:
78
78
  case 18:
79
- case 23:
79
+ case 23: {
80
80
  if (str.charAt(i) !== "-") {
81
81
  return false;
82
82
  }
83
83
  break;
84
+ }
84
85
 
85
- case 14:
86
+ case 14: {
86
87
  if (str.charAt(i) !== "4") {
87
88
  return false;
88
89
  }
89
90
  break;
91
+ }
90
92
 
91
93
  case 19: {
92
94
  const char = str.charAt(i);
@@ -96,11 +98,14 @@ export function isStableId(str: string): str is StableId {
96
98
  break;
97
99
  }
98
100
 
99
- default:
100
- if (!isHexadecimalCharacter(str.charCodeAt(i))) {
101
+ default: {
102
+ const codePoint = str.codePointAt(i);
103
+ assert(codePoint !== undefined, 0xaaf /* Unexpected undefined code point */);
104
+ if (!isHexadecimalCharacter(codePoint)) {
101
105
  return false;
102
106
  }
103
107
  break;
108
+ }
104
109
  }
105
110
  }
106
111
 
@@ -172,10 +177,10 @@ export function stableIdFromNumericUuid(numericUuid: NumericUuid): StableId {
172
177
  const uuidU128 = upperMasked | versionMask | middieBittiesMasked | variantMask | lowerMasked;
173
178
  // Pad to 32 characters, inserting leading zeroes if needed
174
179
  const uuidString = uuidU128.toString(16).padStart(32, "0");
175
- return `${uuidString.substring(0, 8)}-${uuidString.substring(8, 12)}-${uuidString.substring(
180
+ return `${uuidString.slice(0, 8)}-${uuidString.slice(8, 12)}-${uuidString.slice(
176
181
  12,
177
182
  16,
178
- )}-${uuidString.substring(16, 20)}-${uuidString.substring(20, 32)}` as StableId;
183
+ )}-${uuidString.slice(16, 20)}-${uuidString.slice(20, 32)}` as StableId;
179
184
  }
180
185
 
181
186
  export function offsetNumericUuid(numericUuid: NumericUuid, offset: number): NumericUuid {