@fluidframework/id-compressor 2.12.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.
- package/.eslintrc.cjs +1 -4
- package/CHANGELOG.md +8 -0
- package/dist/appendOnlySortedMap.d.ts +37 -16
- package/dist/appendOnlySortedMap.d.ts.map +1 -1
- package/dist/appendOnlySortedMap.js +39 -21
- package/dist/appendOnlySortedMap.js.map +1 -1
- package/dist/finalSpace.d.ts +2 -2
- package/dist/finalSpace.d.ts.map +1 -1
- package/dist/finalSpace.js +5 -5
- package/dist/finalSpace.js.map +1 -1
- package/dist/idCompressor.d.ts.map +1 -1
- package/dist/idCompressor.js +18 -15
- package/dist/idCompressor.js.map +1 -1
- package/dist/identifiers.d.ts +1 -1
- package/dist/identifiers.js +1 -1
- package/dist/identifiers.js.map +1 -1
- package/dist/package.json +2 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/sessions.js.map +1 -1
- package/dist/test/appendOnlySortedMap.perf.spec.js.map +1 -1
- package/dist/test/appendOnlySortedMap.spec.js.map +1 -1
- package/dist/test/idCompressor.perf.spec.js.map +1 -1
- package/dist/test/idCompressor.spec.js.map +1 -1
- package/dist/test/idCompressorTestUtilities.d.ts +39 -13
- package/dist/test/idCompressorTestUtilities.d.ts.map +1 -1
- package/dist/test/idCompressorTestUtilities.js +68 -59
- package/dist/test/idCompressorTestUtilities.js.map +1 -1
- package/dist/test/numericUuid.spec.js.map +1 -1
- package/dist/test/sessionSpaceNormalizer.spec.js.map +1 -1
- package/dist/test/snapshots/summary.spec.js.map +1 -1
- package/dist/test/testCommon.d.ts +5 -1
- package/dist/test/testCommon.d.ts.map +1 -1
- package/dist/test/testCommon.js +2 -0
- package/dist/test/testCommon.js.map +1 -1
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +11 -8
- package/dist/utilities.js.map +1 -1
- package/lib/appendOnlySortedMap.d.ts +37 -16
- package/lib/appendOnlySortedMap.d.ts.map +1 -1
- package/lib/appendOnlySortedMap.js +39 -21
- package/lib/appendOnlySortedMap.js.map +1 -1
- package/lib/finalSpace.d.ts +2 -2
- package/lib/finalSpace.d.ts.map +1 -1
- package/lib/finalSpace.js +5 -5
- package/lib/finalSpace.js.map +1 -1
- package/lib/idCompressor.d.ts.map +1 -1
- package/lib/idCompressor.js +18 -15
- package/lib/idCompressor.js.map +1 -1
- package/lib/identifiers.d.ts +1 -1
- package/lib/identifiers.js +1 -1
- package/lib/identifiers.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/sessions.js.map +1 -1
- package/lib/test/appendOnlySortedMap.perf.spec.js.map +1 -1
- package/lib/test/appendOnlySortedMap.spec.js.map +1 -1
- package/lib/test/idCompressor.perf.spec.js.map +1 -1
- package/lib/test/idCompressor.spec.js.map +1 -1
- package/lib/test/idCompressorTestUtilities.d.ts +39 -13
- package/lib/test/idCompressorTestUtilities.d.ts.map +1 -1
- package/lib/test/idCompressorTestUtilities.js +43 -34
- package/lib/test/idCompressorTestUtilities.js.map +1 -1
- package/lib/test/numericUuid.spec.js.map +1 -1
- package/lib/test/sessionSpaceNormalizer.spec.js.map +1 -1
- package/lib/test/snapshots/summary.spec.js.map +1 -1
- package/lib/test/testCommon.d.ts +5 -1
- package/lib/test/testCommon.d.ts.map +1 -1
- package/lib/test/testCommon.js +2 -0
- package/lib/test/testCommon.js.map +1 -1
- package/lib/utilities.d.ts.map +1 -1
- package/lib/utilities.js +11 -8
- package/lib/utilities.js.map +1 -1
- package/package.json +9 -9
- package/src/appendOnlySortedMap.ts +39 -21
- package/src/finalSpace.ts +6 -6
- package/src/idCompressor.ts +22 -18
- package/src/identifiers.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/sessions.ts +2 -2
- package/src/test/idCompressorTestUtilities.ts +70 -41
- package/src/test/testCommon.ts +5 -1
- package/src/utilities.ts +12 -9
package/src/idCompressor.ts
CHANGED
|
@@ -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
|
|
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 } =
|
|
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
|
-
|
|
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
|
|
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,
|
|
607
|
-
index = writeNumber(serializedFloat, index,
|
|
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
|
-
|
|
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
|
|
656
|
+
case 1: {
|
|
656
657
|
throw new Error("IdCompressor version 1.0 is no longer supported.");
|
|
657
|
-
|
|
658
|
+
}
|
|
659
|
+
case 2: {
|
|
658
660
|
return IdCompressor.deserialize2_0(index, sessionId, logger);
|
|
659
|
-
|
|
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 (
|
|
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++) {
|
package/src/identifiers.ts
CHANGED
|
@@ -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
|
-
*
|
|
29
|
+
* Returns true if the supplied ID is a final ID.
|
|
30
30
|
*/
|
|
31
31
|
export function isFinalId(
|
|
32
32
|
id: SessionSpaceCompressedId | OpSpaceCompressedId,
|
package/src/packageVersion.ts
CHANGED
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Identifies categories of compressors
|
|
66
|
+
*/
|
|
62
67
|
export enum MetaClient {
|
|
63
68
|
All = "All",
|
|
64
69
|
}
|
|
65
70
|
|
|
66
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* The compressors used in this network
|
|
200
|
+
*/
|
|
188
201
|
private readonly compressors: ClientMap<IdCompressor>;
|
|
189
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
395
|
-
? this.
|
|
396
|
-
: this.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
@@ -638,7 +661,7 @@ export class IdCompressorTestNetwork {
|
|
|
638
661
|
assert(originatingSession !== undefined, "Expected originating client to be defined");
|
|
639
662
|
idIndicesAggregator.set(
|
|
640
663
|
originatingSession,
|
|
641
|
-
|
|
664
|
+
|
|
642
665
|
(idIndicesAggregator.get(originatingSession) ??
|
|
643
666
|
fail("Expected pre-existing index for originating client")) + 1,
|
|
644
667
|
);
|
|
@@ -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
|
|
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
|
-
/**
|
|
822
|
+
/**
|
|
823
|
+
* maximum cluster size of the network. Default: 25
|
|
824
|
+
*/
|
|
800
825
|
maxClusterSize?: number;
|
|
801
|
-
/**
|
|
826
|
+
/**
|
|
827
|
+
* Number of ops between validation ops. Default: 200
|
|
828
|
+
*/
|
|
802
829
|
validateInterval?: number;
|
|
803
|
-
/**
|
|
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
|
|
package/src/test/testCommon.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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 =
|
|
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
|
-
|
|
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,14 +177,13 @@ 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.
|
|
180
|
+
return `${uuidString.slice(0, 8)}-${uuidString.slice(8, 12)}-${uuidString.slice(
|
|
176
181
|
12,
|
|
177
182
|
16,
|
|
178
|
-
)}-${uuidString.
|
|
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 {
|
|
182
|
-
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
183
187
|
return (numericUuid + BigInt(offset)) as NumericUuid;
|
|
184
188
|
}
|
|
185
189
|
|
|
@@ -188,6 +192,5 @@ export function subtractNumericUuids(a: NumericUuid, b: NumericUuid): NumericUui
|
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
export function addNumericUuids(a: NumericUuid, b: NumericUuid): NumericUuid {
|
|
191
|
-
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
192
195
|
return (a + b) as NumericUuid;
|
|
193
196
|
}
|