@fluidframework/id-compressor 3.3.0 → 3.5.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/CHANGELOG.md +8 -0
- package/dist/idCompressor.d.ts +10 -17
- package/dist/idCompressor.d.ts.map +1 -1
- package/dist/idCompressor.js +68 -35
- package/dist/idCompressor.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/test/sharding.fuzz.spec.js.map +1 -1
- package/dist/test/sharding.spec.js.map +1 -1
- package/dist/test/types/validateIdCompressorPrevious.generated.js.map +1 -1
- package/dist/types/idCompressor.d.ts +110 -31
- package/dist/types/idCompressor.d.ts.map +1 -1
- package/dist/types/idCompressor.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/lib/idCompressor.d.ts +10 -17
- package/lib/idCompressor.d.ts.map +1 -1
- package/lib/idCompressor.js +69 -36
- package/lib/idCompressor.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.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/test/sharding.fuzz.spec.js.map +1 -1
- package/lib/test/sharding.spec.js.map +1 -1
- package/lib/test/types/validateIdCompressorPrevious.generated.js.map +1 -1
- package/lib/types/idCompressor.d.ts +110 -31
- package/lib/types/idCompressor.d.ts.map +1 -1
- package/lib/types/idCompressor.js.map +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js.map +1 -1
- package/package.json +9 -42
- package/src/idCompressor.ts +90 -42
- package/src/index.ts +10 -7
- package/src/packageVersion.ts +1 -1
- package/src/test/types/validateIdCompressorPrevious.generated.ts +1 -121
- package/src/types/idCompressor.ts +119 -31
- package/src/types/index.ts +3 -0
package/src/idCompressor.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
|
|
7
|
-
import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
6
|
+
import { bufferToString, createEmitter, stringToBuffer } from "@fluid-internal/client-utils";
|
|
7
|
+
import type { ITelemetryBaseLogger, Listenable } from "@fluidframework/core-interfaces";
|
|
8
8
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
9
9
|
import type { TelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
10
10
|
import {
|
|
@@ -54,6 +54,9 @@ import type {
|
|
|
54
54
|
SessionSpaceCompressedId,
|
|
55
55
|
StableId,
|
|
56
56
|
ShardSynchronizationToken,
|
|
57
|
+
SerializedIdCompressorShard,
|
|
58
|
+
ParentShardSynchronizationToken,
|
|
59
|
+
IdCompressorEvents,
|
|
57
60
|
} from "./types/index.js";
|
|
58
61
|
import { SerializationVersion } from "./types/index.js";
|
|
59
62
|
import {
|
|
@@ -75,10 +78,26 @@ function rangeFinalizationError(expectedStart: number, actualStart: number): Log
|
|
|
75
78
|
|
|
76
79
|
const MAX_STRIDE_LENGTH = 1000000;
|
|
77
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Validates the generation count in a shard synchronization token.
|
|
83
|
+
*
|
|
84
|
+
* @param localGenCount - The number of positions filled, including backfilled positions.
|
|
85
|
+
* @throws {@link TypeError} if the count is not a nonnegative safe integer.
|
|
86
|
+
*/
|
|
87
|
+
function validateShardGenerationCount(localGenCount: number): void {
|
|
88
|
+
if (!Number.isSafeInteger(localGenCount) || localGenCount < 0) {
|
|
89
|
+
throw new TypeError("Invalid shard synchronization token generation count.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
/**
|
|
79
94
|
* See {@link IIdCompressor} and {@link IIdCompressorCore}
|
|
80
95
|
*/
|
|
81
96
|
export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
97
|
+
private readonly eventEmitter = createEmitter<IdCompressorEvents>();
|
|
98
|
+
|
|
99
|
+
public readonly events: Listenable<IdCompressorEvents> = this.eventEmitter;
|
|
100
|
+
|
|
82
101
|
/**
|
|
83
102
|
* Max allowed initial cluster size.
|
|
84
103
|
*/
|
|
@@ -261,9 +280,6 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
261
280
|
this.ongoingGhostSession = { ghostSessionId };
|
|
262
281
|
}
|
|
263
282
|
|
|
264
|
-
/**
|
|
265
|
-
* {@inheritdoc IIdCompressorCore.beginGhostSession}
|
|
266
|
-
*/
|
|
267
283
|
public beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void): void {
|
|
268
284
|
if (this.shardingState) {
|
|
269
285
|
throw new Error("Cannot start a ghost session while sharded.");
|
|
@@ -276,10 +292,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
276
292
|
}
|
|
277
293
|
}
|
|
278
294
|
|
|
279
|
-
|
|
280
|
-
* {@inheritdoc IIdCompressorCore.shard}
|
|
281
|
-
*/
|
|
282
|
-
public shard(newShardCount: number): SerializedIdCompressorWithOngoingSession[] {
|
|
295
|
+
public shard(newShardCount: number): SerializedIdCompressorShard[] {
|
|
283
296
|
if (!Number.isSafeInteger(newShardCount) || newShardCount <= 0) {
|
|
284
297
|
throw new Error("Shard count must be a positive safe integer");
|
|
285
298
|
}
|
|
@@ -316,7 +329,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
316
329
|
const serialized = this.serialize(true);
|
|
317
330
|
const { localGenCount: parentGenCount } = this;
|
|
318
331
|
|
|
319
|
-
const shards:
|
|
332
|
+
const shards: SerializedIdCompressorShard[] = [];
|
|
320
333
|
|
|
321
334
|
// Children are positioned at consecutive positions within the parents current stride cycle
|
|
322
335
|
// e.g. if parent owns the "evens" space (-2, -4, -6, ...) and is sharded into 3 (two children)
|
|
@@ -346,14 +359,15 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
346
359
|
shardId: childShardId,
|
|
347
360
|
};
|
|
348
361
|
|
|
349
|
-
shards.push(
|
|
362
|
+
shards.push({
|
|
363
|
+
serialized: child.serialize(true),
|
|
364
|
+
syncToken: child.makeShardToken(false),
|
|
365
|
+
});
|
|
350
366
|
}
|
|
351
367
|
|
|
352
368
|
return shards;
|
|
353
369
|
}
|
|
354
|
-
|
|
355
|
-
* {@inheritdoc IIdCompressorCore.synchronizeWithShard}
|
|
356
|
-
*/
|
|
370
|
+
|
|
357
371
|
public synchronizeWithShard(syncToken: ShardSynchronizationToken): void {
|
|
358
372
|
const isNowLeaf = this.synchronizeChild(syncToken);
|
|
359
373
|
// A disposal token additionally reclaims the child's ID space. If reclaiming the last child
|
|
@@ -366,6 +380,54 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
366
380
|
}
|
|
367
381
|
}
|
|
368
382
|
|
|
383
|
+
public getChildShardSyncToken(
|
|
384
|
+
childToken: ShardSynchronizationToken,
|
|
385
|
+
): ParentShardSynchronizationToken {
|
|
386
|
+
if (!this.shardingState?.activeChildIds.has(childToken.shardId)) {
|
|
387
|
+
throw new Error("Cannot get a synchronization token for an inactive child shard.");
|
|
388
|
+
}
|
|
389
|
+
const token: ParentShardSynchronizationToken = {
|
|
390
|
+
type: "parentIdSpaceShardSyncToken",
|
|
391
|
+
shardId: childToken.shardId,
|
|
392
|
+
localGenCount: this.localGenCount,
|
|
393
|
+
};
|
|
394
|
+
return token;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
public synchronizeWithParent(token: ParentShardSynchronizationToken): void {
|
|
398
|
+
const state = this.shardingState;
|
|
399
|
+
if (
|
|
400
|
+
token.type !== "parentIdSpaceShardSyncToken" ||
|
|
401
|
+
state?.shardId === undefined ||
|
|
402
|
+
state.shardId !== token.shardId
|
|
403
|
+
) {
|
|
404
|
+
throw new Error("Invalid parent synchronization token for this child shard.");
|
|
405
|
+
}
|
|
406
|
+
validateShardGenerationCount(token.localGenCount);
|
|
407
|
+
if (token.localGenCount <= this.localGenCount) {
|
|
408
|
+
// A child can already be ahead through its own allocations. Its local range
|
|
409
|
+
// then includes the parent's IDs through this generation count.
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.backfillToNextStridePosition(token.localGenCount, state.currentStride);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Adds IDs through the next position in this shard's stride after the given generation count.
|
|
417
|
+
*/
|
|
418
|
+
private backfillToNextStridePosition(targetGenCount: number, stride: number): void {
|
|
419
|
+
if (targetGenCount <= this.localGenCount) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const count = Math.ceil((targetGenCount - this.localGenCount + 1) / stride) * stride;
|
|
423
|
+
const nextGenCount = this.localGenCount + count;
|
|
424
|
+
if (!Number.isSafeInteger(nextGenCount)) {
|
|
425
|
+
throw new TypeError("Shard progress exceeds the supported ID space.");
|
|
426
|
+
}
|
|
427
|
+
this.normalizer.addLocalRange(this.localGenCount + 1, count);
|
|
428
|
+
this.localGenCount = nextGenCount;
|
|
429
|
+
}
|
|
430
|
+
|
|
369
431
|
private synchronizeChild(syncToken: ShardToken): boolean {
|
|
370
432
|
const childDisposed = syncToken.disposed;
|
|
371
433
|
if (this.writeVersion < SerializationVersion.V3) {
|
|
@@ -386,39 +448,27 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
386
448
|
`Cannot synchronize with child with ID ${childShardId}: not in active children set`,
|
|
387
449
|
);
|
|
388
450
|
}
|
|
451
|
+
validateShardGenerationCount(syncToken.localGenCount);
|
|
452
|
+
|
|
453
|
+
// A last-child disposal realigns on the original stride. Backfill before changing
|
|
454
|
+
// membership so invalid progress cannot accidentally reclaim the child.
|
|
455
|
+
const isLeaf = childDisposed && activeChildIds.size === 1;
|
|
456
|
+
this.backfillToNextStridePosition(
|
|
457
|
+
syncToken.localGenCount,
|
|
458
|
+
isLeaf ? originalStride : this.shardingState.currentStride,
|
|
459
|
+
);
|
|
389
460
|
|
|
390
|
-
//
|
|
391
|
-
// child active so it can continue generating IDs and be synchronized with again later.
|
|
461
|
+
// A plain synchronization leaves the child active so it can keep generating IDs.
|
|
392
462
|
if (childDisposed) {
|
|
393
463
|
activeChildIds.delete(childShardId);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (isLeaf && childDisposed) {
|
|
398
|
-
this.shardingState.currentStride = originalStride;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Read currentStride after the possible stride reset above, so that a leaf-making disposal
|
|
402
|
-
// realigns the parent on its original (reclaimed) stride rather than the wider sharded stride.
|
|
403
|
-
const { currentStride } = this.shardingState;
|
|
404
|
-
const childGenCount = syncToken.localGenCount;
|
|
405
|
-
|
|
406
|
-
// Realign parent to next position in its sequence if child is ahead
|
|
407
|
-
if (childGenCount > this.localGenCount) {
|
|
408
|
-
// Find next aligned position in parent's sequence after child's genCount
|
|
409
|
-
const distance = childGenCount - this.localGenCount + 1;
|
|
410
|
-
const steps = Math.ceil(distance / currentStride);
|
|
411
|
-
const count = steps * currentStride;
|
|
412
|
-
this.normalizer.addLocalRange(this.localGenCount + 1, count);
|
|
413
|
-
this.localGenCount += count;
|
|
464
|
+
if (isLeaf) {
|
|
465
|
+
this.shardingState.currentStride = originalStride;
|
|
466
|
+
}
|
|
414
467
|
}
|
|
415
468
|
|
|
416
469
|
return isLeaf;
|
|
417
470
|
}
|
|
418
471
|
|
|
419
|
-
/**
|
|
420
|
-
* {@inheritdoc IIdCompressorCore.dispose}
|
|
421
|
-
*/
|
|
422
472
|
public disposeShard(): ShardSynchronizationToken | undefined {
|
|
423
473
|
if (this.shardingState === undefined) {
|
|
424
474
|
return undefined;
|
|
@@ -438,9 +488,6 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
438
488
|
return token;
|
|
439
489
|
}
|
|
440
490
|
|
|
441
|
-
/**
|
|
442
|
-
* {@inheritdoc IIdCompressorCore.getShardSyncToken}
|
|
443
|
-
*/
|
|
444
491
|
public getShardSyncToken(): ShardSynchronizationToken | undefined {
|
|
445
492
|
if (this.shardingState === undefined) {
|
|
446
493
|
return undefined;
|
|
@@ -639,6 +686,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
639
686
|
}
|
|
640
687
|
|
|
641
688
|
assert(!session.isEmpty(), 0x757 /* Empty sessions should not be created. */);
|
|
689
|
+
this.eventEmitter.emit("rangeFinalized", range);
|
|
642
690
|
}
|
|
643
691
|
|
|
644
692
|
private addEmptyCluster(session: Session, capacity: number): IdCluster {
|
package/src/index.ts
CHANGED
|
@@ -22,16 +22,19 @@ export {
|
|
|
22
22
|
isStableId,
|
|
23
23
|
} from "./utilities.js";
|
|
24
24
|
export type {
|
|
25
|
-
|
|
25
|
+
IdCreationRange,
|
|
26
26
|
IIdCompressor,
|
|
27
|
+
IIdCompressorCore,
|
|
28
|
+
IdCompressorEvents,
|
|
29
|
+
OpSpaceCompressedId,
|
|
30
|
+
ParentShardSynchronizationToken,
|
|
27
31
|
SerializedIdCompressor,
|
|
28
|
-
SerializedIdCompressorWithOngoingSession,
|
|
29
32
|
SerializedIdCompressorWithNoSession,
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
SerializedIdCompressorWithOngoingSession,
|
|
34
|
+
SerializedIdCompressorShard,
|
|
32
35
|
SessionId,
|
|
33
|
-
|
|
34
|
-
IdCreationRange,
|
|
35
|
-
ShardToken,
|
|
36
|
+
SessionSpaceCompressedId,
|
|
36
37
|
ShardSynchronizationToken,
|
|
38
|
+
ShardToken,
|
|
39
|
+
StableId,
|
|
37
40
|
} from "./types/index.js";
|
package/src/packageVersion.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
8
8
|
* Generated by "flub generate typetests" from @fluid-tools/build-cli.
|
|
9
9
|
*
|
|
10
|
-
* Baseline (previous) version: 3.
|
|
10
|
+
* Baseline (previous) version: 3.4.0
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { TypeOnly, MinimalType, FullType, requireAssignableTo } from "@fluidframework/build-tools";
|
|
@@ -17,66 +17,6 @@ import type * as current from "@fluidframework/id-compressor/legacy";
|
|
|
17
17
|
|
|
18
18
|
declare type MakeUnusedImportErrorsGoAway<T> = TypeOnly<T> | MinimalType<T> | FullType<T> | typeof old | typeof current | requireAssignableTo<true, true>;
|
|
19
19
|
|
|
20
|
-
/*
|
|
21
|
-
* Validate backward compatibility by using the current type in place of the old type.
|
|
22
|
-
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
23
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
24
|
-
* typeValidation.broken:
|
|
25
|
-
* "Function_createIdCompressor": {"backCompat": false}
|
|
26
|
-
*/
|
|
27
|
-
// @ts-expect-error compatibility expected to be broken
|
|
28
|
-
declare type current_as_old_for_Function_createIdCompressor = requireAssignableTo<TypeOnly<typeof current.createIdCompressor>, TypeOnly<typeof old.createIdCompressor>>
|
|
29
|
-
|
|
30
|
-
/*
|
|
31
|
-
* Validate backward compatibility by using the current type in place of the old type.
|
|
32
|
-
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
33
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
34
|
-
* typeValidation.broken:
|
|
35
|
-
* "Function_createSessionId": {"backCompat": false}
|
|
36
|
-
*/
|
|
37
|
-
// @ts-expect-error compatibility expected to be broken
|
|
38
|
-
declare type current_as_old_for_Function_createSessionId = requireAssignableTo<TypeOnly<typeof current.createSessionId>, TypeOnly<typeof old.createSessionId>>
|
|
39
|
-
|
|
40
|
-
/*
|
|
41
|
-
* Validate backward compatibility by using the current type in place of the old type.
|
|
42
|
-
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
43
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
44
|
-
* typeValidation.broken:
|
|
45
|
-
* "Function_deserializeIdCompressor": {"backCompat": false}
|
|
46
|
-
*/
|
|
47
|
-
// @ts-expect-error compatibility expected to be broken
|
|
48
|
-
declare type current_as_old_for_Function_deserializeIdCompressor = requireAssignableTo<TypeOnly<typeof current.deserializeIdCompressor>, TypeOnly<typeof old.deserializeIdCompressor>>
|
|
49
|
-
|
|
50
|
-
/*
|
|
51
|
-
* Validate backward compatibility by using the current type in place of the old type.
|
|
52
|
-
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
53
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
54
|
-
* typeValidation.broken:
|
|
55
|
-
* "Function_serializeIdCompressor": {"backCompat": false}
|
|
56
|
-
*/
|
|
57
|
-
// @ts-expect-error compatibility expected to be broken
|
|
58
|
-
declare type current_as_old_for_Function_serializeIdCompressor = requireAssignableTo<TypeOnly<typeof current.serializeIdCompressor>, TypeOnly<typeof old.serializeIdCompressor>>
|
|
59
|
-
|
|
60
|
-
/*
|
|
61
|
-
* Validate forward compatibility by using the old type in place of the current type.
|
|
62
|
-
* If this test starts failing, it indicates a change that is not forward compatible.
|
|
63
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
64
|
-
* typeValidation.broken:
|
|
65
|
-
* "Interface_IdCreationRange": {"forwardCompat": false}
|
|
66
|
-
*/
|
|
67
|
-
// @ts-expect-error compatibility expected to be broken
|
|
68
|
-
declare type old_as_current_for_Interface_IdCreationRange = requireAssignableTo<TypeOnly<old.IdCreationRange>, TypeOnly<current.IdCreationRange>>
|
|
69
|
-
|
|
70
|
-
/*
|
|
71
|
-
* Validate backward compatibility by using the current type in place of the old type.
|
|
72
|
-
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
73
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
74
|
-
* typeValidation.broken:
|
|
75
|
-
* "Interface_IdCreationRange": {"backCompat": false}
|
|
76
|
-
*/
|
|
77
|
-
// @ts-expect-error compatibility expected to be broken
|
|
78
|
-
declare type current_as_old_for_Interface_IdCreationRange = requireAssignableTo<TypeOnly<current.IdCreationRange>, TypeOnly<old.IdCreationRange>>
|
|
79
|
-
|
|
80
20
|
/*
|
|
81
21
|
* Validate backward compatibility by using the current type in place of the old type.
|
|
82
22
|
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
@@ -104,66 +44,6 @@ declare type old_as_current_for_TypeAlias_OpSpaceCompressedId = requireAssignabl
|
|
|
104
44
|
*/
|
|
105
45
|
declare type current_as_old_for_TypeAlias_OpSpaceCompressedId = requireAssignableTo<TypeOnly<current.OpSpaceCompressedId>, TypeOnly<old.OpSpaceCompressedId>>
|
|
106
46
|
|
|
107
|
-
/*
|
|
108
|
-
* Validate forward compatibility by using the old type in place of the current type.
|
|
109
|
-
* If this test starts failing, it indicates a change that is not forward compatible.
|
|
110
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
111
|
-
* typeValidation.broken:
|
|
112
|
-
* "TypeAlias_SerializedIdCompressor": {"forwardCompat": false}
|
|
113
|
-
*/
|
|
114
|
-
// @ts-expect-error compatibility expected to be broken
|
|
115
|
-
declare type old_as_current_for_TypeAlias_SerializedIdCompressor = requireAssignableTo<TypeOnly<old.SerializedIdCompressor>, TypeOnly<current.SerializedIdCompressor>>
|
|
116
|
-
|
|
117
|
-
/*
|
|
118
|
-
* Validate backward compatibility by using the current type in place of the old type.
|
|
119
|
-
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
120
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
121
|
-
* typeValidation.broken:
|
|
122
|
-
* "TypeAlias_SerializedIdCompressor": {"backCompat": false}
|
|
123
|
-
*/
|
|
124
|
-
// @ts-expect-error compatibility expected to be broken
|
|
125
|
-
declare type current_as_old_for_TypeAlias_SerializedIdCompressor = requireAssignableTo<TypeOnly<current.SerializedIdCompressor>, TypeOnly<old.SerializedIdCompressor>>
|
|
126
|
-
|
|
127
|
-
/*
|
|
128
|
-
* Validate forward compatibility by using the old type in place of the current type.
|
|
129
|
-
* If this test starts failing, it indicates a change that is not forward compatible.
|
|
130
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
131
|
-
* typeValidation.broken:
|
|
132
|
-
* "TypeAlias_SerializedIdCompressorWithNoSession": {"forwardCompat": false}
|
|
133
|
-
*/
|
|
134
|
-
// @ts-expect-error compatibility expected to be broken
|
|
135
|
-
declare type old_as_current_for_TypeAlias_SerializedIdCompressorWithNoSession = requireAssignableTo<TypeOnly<old.SerializedIdCompressorWithNoSession>, TypeOnly<current.SerializedIdCompressorWithNoSession>>
|
|
136
|
-
|
|
137
|
-
/*
|
|
138
|
-
* Validate backward compatibility by using the current type in place of the old type.
|
|
139
|
-
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
140
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
141
|
-
* typeValidation.broken:
|
|
142
|
-
* "TypeAlias_SerializedIdCompressorWithNoSession": {"backCompat": false}
|
|
143
|
-
*/
|
|
144
|
-
// @ts-expect-error compatibility expected to be broken
|
|
145
|
-
declare type current_as_old_for_TypeAlias_SerializedIdCompressorWithNoSession = requireAssignableTo<TypeOnly<current.SerializedIdCompressorWithNoSession>, TypeOnly<old.SerializedIdCompressorWithNoSession>>
|
|
146
|
-
|
|
147
|
-
/*
|
|
148
|
-
* Validate forward compatibility by using the old type in place of the current type.
|
|
149
|
-
* If this test starts failing, it indicates a change that is not forward compatible.
|
|
150
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
151
|
-
* typeValidation.broken:
|
|
152
|
-
* "TypeAlias_SerializedIdCompressorWithOngoingSession": {"forwardCompat": false}
|
|
153
|
-
*/
|
|
154
|
-
// @ts-expect-error compatibility expected to be broken
|
|
155
|
-
declare type old_as_current_for_TypeAlias_SerializedIdCompressorWithOngoingSession = requireAssignableTo<TypeOnly<old.SerializedIdCompressorWithOngoingSession>, TypeOnly<current.SerializedIdCompressorWithOngoingSession>>
|
|
156
|
-
|
|
157
|
-
/*
|
|
158
|
-
* Validate backward compatibility by using the current type in place of the old type.
|
|
159
|
-
* If this test starts failing, it indicates a change that is not backward compatible.
|
|
160
|
-
* To acknowledge the breaking change, add the following to package.json under
|
|
161
|
-
* typeValidation.broken:
|
|
162
|
-
* "TypeAlias_SerializedIdCompressorWithOngoingSession": {"backCompat": false}
|
|
163
|
-
*/
|
|
164
|
-
// @ts-expect-error compatibility expected to be broken
|
|
165
|
-
declare type current_as_old_for_TypeAlias_SerializedIdCompressorWithOngoingSession = requireAssignableTo<TypeOnly<current.SerializedIdCompressorWithOngoingSession>, TypeOnly<old.SerializedIdCompressorWithOngoingSession>>
|
|
166
|
-
|
|
167
47
|
/*
|
|
168
48
|
* Validate forward compatibility by using the old type in place of the current type.
|
|
169
49
|
* If this test starts failing, it indicates a change that is not forward compatible.
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
SessionSpaceCompressedId,
|
|
10
10
|
StableId,
|
|
11
11
|
} from "./identifiers.js";
|
|
12
|
+
import type { Listenable } from "@fluidframework/core-interfaces";
|
|
12
13
|
import type {
|
|
13
14
|
IdCreationRange,
|
|
14
15
|
SerializedIdCompressorWithNoSession,
|
|
@@ -101,6 +102,11 @@ export type SerializationVersion =
|
|
|
101
102
|
* @internal
|
|
102
103
|
*/
|
|
103
104
|
export interface IIdCompressorCore {
|
|
105
|
+
/**
|
|
106
|
+
* Events emitted by the ID compressor.
|
|
107
|
+
*/
|
|
108
|
+
readonly events: Listenable<IdCompressorEvents>;
|
|
109
|
+
|
|
104
110
|
/**
|
|
105
111
|
* Returns a range of IDs created by this session in a format for sending to the server for finalizing.
|
|
106
112
|
* The range will include all IDs generated via calls to `generateCompressedId` since the last time a
|
|
@@ -174,12 +180,11 @@ export interface IIdCompressorCore {
|
|
|
174
180
|
* @param newShardCount - The number of additional different shards to split this compressor into.
|
|
175
181
|
* Must be a positive safe integer.
|
|
176
182
|
* @throws If `newShardCount` is not a positive safe integer or the resulting stride exceeds the sharding limit.
|
|
177
|
-
* @returns An array of
|
|
178
|
-
* These can be passed across a marshalling boundary and rehydrated on the other side, and will safely share the ID space of `this`.
|
|
183
|
+
* @returns An array of `newShardCount` {@link SerializedIdCompressorShard} objects.
|
|
179
184
|
* Note that this method should only be needed when multiple JS runtimes are in play, as sharded compressors essentially
|
|
180
185
|
* attempt to emulate a single static compressor and any code running in the same JS runtime can simply use statics.
|
|
181
186
|
*/
|
|
182
|
-
shard(newShardCount: number):
|
|
187
|
+
shard(newShardCount: number): SerializedIdCompressorShard[];
|
|
183
188
|
|
|
184
189
|
/**
|
|
185
190
|
* Synchronizes `this` compressor with a child shard. Synchronization will occur for the state of the child at the time `syncToken`
|
|
@@ -192,9 +197,37 @@ export interface IIdCompressorCore {
|
|
|
192
197
|
* be disposed from the leaves upwards.
|
|
193
198
|
* @param syncToken - The token for the shard, obtained by calling {@link IIdCompressorCore.getShardSyncToken} (non-destructive
|
|
194
199
|
* synchronization) or {@link IIdCompressorCore.disposeShard} (synchronization plus reclamation of the disposed shard's ID space).
|
|
200
|
+
* @throws `TypeError` if the token's generation count is not a nonnegative safe integer.
|
|
195
201
|
*/
|
|
196
202
|
synchronizeWithShard(syncToken: ShardSynchronizationToken): void;
|
|
197
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Gets a synchronization token from this compressor for an active child shard.
|
|
206
|
+
*
|
|
207
|
+
* @remarks
|
|
208
|
+
* The child can apply this token without replacing its compressor.
|
|
209
|
+
*
|
|
210
|
+
* @param childToken - A token from the child shard that will receive this token.
|
|
211
|
+
* @returns This parent's synchronization token for that child.
|
|
212
|
+
* @throws If the child shard is not active on this compressor.
|
|
213
|
+
*/
|
|
214
|
+
getChildShardSyncToken(
|
|
215
|
+
childToken: ShardSynchronizationToken,
|
|
216
|
+
): ParentShardSynchronizationToken;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Synchronizes this child shard with its parent's token.
|
|
220
|
+
*
|
|
221
|
+
* @remarks
|
|
222
|
+
* This method keeps the child's allocation stride and does not finalize ID creation ranges.
|
|
223
|
+
* Apply creation ranges from {@link IIdCompressorCore.events} separately, in order.
|
|
224
|
+
*
|
|
225
|
+
* @param token - Synchronization token obtained from this child's parent.
|
|
226
|
+
* @throws If the token names another child or contains invalid state.
|
|
227
|
+
* @throws `TypeError` if the token's generation count is not a nonnegative safe integer.
|
|
228
|
+
*/
|
|
229
|
+
synchronizeWithParent(token: ParentShardSynchronizationToken): void;
|
|
230
|
+
|
|
198
231
|
/**
|
|
199
232
|
* Returns undefined if this compressor is not part of a shard group, and otherwise returns a synchronization token for this shard
|
|
200
233
|
* that can be used when calling {@link IIdCompressorCore.synchronizeWithShard}. This does NOT dispose the shard.
|
|
@@ -234,24 +267,47 @@ export interface IIdCompressorCore {
|
|
|
234
267
|
}
|
|
235
268
|
|
|
236
269
|
/**
|
|
237
|
-
* The state
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
|
|
270
|
+
* The serialized state of a new child compressor and its initial synchronization token.
|
|
271
|
+
*
|
|
272
|
+
* @sealed
|
|
273
|
+
* @internal
|
|
274
|
+
*/
|
|
275
|
+
export interface SerializedIdCompressorShard {
|
|
276
|
+
/**
|
|
277
|
+
* Serialized child state that can be rehydrated across a marshalling boundary.
|
|
278
|
+
*/
|
|
279
|
+
readonly serialized: SerializedIdCompressorWithOngoingSession;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Initial, non-disposing token for this child shard.
|
|
283
|
+
*
|
|
284
|
+
* @remarks
|
|
285
|
+
* After the child generates IDs, obtain its updated token before synchronizing
|
|
286
|
+
* with the parent or reclaiming the child's ID space.
|
|
287
|
+
*
|
|
288
|
+
* This can be used to immediately dispose of a new shard if it is not used.
|
|
289
|
+
*/
|
|
290
|
+
readonly syncToken: ShardSynchronizationToken;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* State shared by shard synchronization tokens.
|
|
295
|
+
* @remarks
|
|
296
|
+
* Treat these tokens as opaque: the compressor owns their interpretation and may add state in the future.
|
|
297
|
+
* The {@link ShardToken.disposed} flag distinguishes ordinary synchronization from shard reclamation.
|
|
298
|
+
* The branded {@link ShardSynchronizationToken} is the concrete child-to-parent token.
|
|
242
299
|
* @internal
|
|
243
300
|
*/
|
|
244
301
|
export interface ShardToken {
|
|
245
302
|
/**
|
|
246
|
-
* The
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* so this value may be non-zero even if the shard hasn't generated any IDs yet.
|
|
303
|
+
* The sending compressor's generation count, which can include backfilled positions
|
|
304
|
+
* rather than only IDs it generated.
|
|
305
|
+
* @remarks Must be a nonnegative safe integer.
|
|
250
306
|
*/
|
|
251
307
|
localGenCount: number;
|
|
252
308
|
|
|
253
309
|
/**
|
|
254
|
-
*
|
|
310
|
+
* Identifies the child shard involved in synchronization with its parent.
|
|
255
311
|
*/
|
|
256
312
|
shardId: SessionId;
|
|
257
313
|
|
|
@@ -272,27 +328,50 @@ export type ShardSynchronizationToken = ShardToken & {
|
|
|
272
328
|
readonly ShardSynchronizationToken: "c79724e1-9103-4415-95b5-bebb932be404";
|
|
273
329
|
};
|
|
274
330
|
|
|
331
|
+
/**
|
|
332
|
+
* A synchronization token from a parent to one of its active child shards.
|
|
333
|
+
* @remarks
|
|
334
|
+
* Unlike the child-to-parent {@link ShardSynchronizationToken}, this token cannot dispose a shard.
|
|
335
|
+
* It does not finalize ID creation ranges.
|
|
336
|
+
* @internal
|
|
337
|
+
*/
|
|
338
|
+
export interface ParentShardSynchronizationToken
|
|
339
|
+
extends Readonly<Omit<ShardToken, "disposed">> {
|
|
340
|
+
/** Identifies the parent-to-child token across a message boundary. */
|
|
341
|
+
readonly type: "parentIdSpaceShardSyncToken";
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Events for finalized ID creation ranges.
|
|
346
|
+
* @internal
|
|
347
|
+
*/
|
|
348
|
+
export interface IdCompressorEvents {
|
|
349
|
+
/** Reports a range after finalization succeeds. */
|
|
350
|
+
rangeFinalized: (range: IdCreationRange) => void;
|
|
351
|
+
}
|
|
275
352
|
/**
|
|
276
353
|
* A distributed UUID generator and compressor.
|
|
354
|
+
*
|
|
355
|
+
* @remarks
|
|
277
356
|
* `IdCompressor` offers the ability to generate arbitrary non-colliding v4 UUIDs, called stable IDs, while compressing them into small integers
|
|
278
357
|
* for efficient storage and transmission. It also provides the ability to decompress these integers back into their original UUIDs.
|
|
279
358
|
*
|
|
280
359
|
* The compressor is designed to be used in a distributed environment, where multiple clients may be generating IDs concurrently.
|
|
281
|
-
* IDs generated by a compressor, via calls to
|
|
360
|
+
* IDs generated by a compressor, via calls to {@link IIdCompressor.generateCompressedId}, are created in the compressor's "local" session.
|
|
282
361
|
* These IDs are unique within the session, but may not be unique across sessions. In the context of Fluid, the scope of a session
|
|
283
362
|
* is the same as the scope of a container. This means that anytime IDs are transferred between sessions, they must be translated
|
|
284
363
|
* through a process called "normalization":
|
|
364
|
+
*
|
|
285
365
|
* - The scope of a local session (in which local IDs are unique) is called the "session space" of the client.
|
|
286
366
|
* - The context of persisted state (in Fluid, this is the context of ops and summaries) is called "op space".
|
|
287
|
-
* -
|
|
367
|
+
* - {@link SessionSpaceCompressedId | SessionSpaceCompressedIds}, generated via calls to {@link IIdCompressor.generateCompressedId}, should NEVER be serialized directly.
|
|
288
368
|
* In Fluid, this means they should not be included in ops or summaries.
|
|
289
369
|
* - Before serialization, IDs must be normalized to op space to ensure they are interpretable by other clients.
|
|
290
370
|
* - Upon receipt, IDs should be normalized back to session space before use.
|
|
291
371
|
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* ### Client A (Sender)
|
|
372
|
+
* @example
|
|
295
373
|
*
|
|
374
|
+
* Client A (Sender)
|
|
296
375
|
* ```typescript
|
|
297
376
|
* // Generate several local IDs
|
|
298
377
|
* const localId1 = idCompressor.generateCompressedId();
|
|
@@ -312,7 +391,7 @@ export type ShardSynchronizationToken = ShardToken & {
|
|
|
312
391
|
* };
|
|
313
392
|
* ```
|
|
314
393
|
*
|
|
315
|
-
*
|
|
394
|
+
* Client B (Receiver)
|
|
316
395
|
*
|
|
317
396
|
* ```typescript
|
|
318
397
|
* // Receive the message from Client A
|
|
@@ -334,27 +413,34 @@ export interface IIdCompressor {
|
|
|
334
413
|
|
|
335
414
|
/**
|
|
336
415
|
* Generates a new compressed ID.
|
|
337
|
-
*
|
|
416
|
+
*
|
|
417
|
+
* @remarks The returned ID is in session space and should not be serialized directly.
|
|
418
|
+
* @see {@link IIdCompressor} for more details.
|
|
338
419
|
* @returns A new local ID in session space.
|
|
339
420
|
*/
|
|
340
421
|
generateCompressedId(): SessionSpaceCompressedId;
|
|
341
422
|
|
|
342
423
|
/**
|
|
343
424
|
* Generates a new ID that is guaranteed to be unique across all sessions known to this compressor without the need for any
|
|
344
|
-
* normalization.
|
|
425
|
+
* normalization.
|
|
426
|
+
*
|
|
427
|
+
* @remarks The returned ID is not guaranteed to be a compressed ID (small number); it may be a stable ID (UUID string).
|
|
345
428
|
* In Fluid, the likelihood of generating the bulkier stable ID is dictated by network conditions and is highly probable in
|
|
346
429
|
* scenarios such as offline. This is still useful for use cases where simplicity is more important than performance and
|
|
347
430
|
* this approach will often be superior to generating a UUID.
|
|
348
|
-
*
|
|
349
|
-
*
|
|
431
|
+
*
|
|
432
|
+
* If small numbers are a requirement, {@link IIdCompressor.generateCompressedId} and normalization should be used instead.
|
|
433
|
+
* @see {@link IIdCompressor} for more details.
|
|
350
434
|
* @returns A new local ID in session space.
|
|
351
435
|
*/
|
|
352
436
|
generateDocumentUniqueId(): (SessionSpaceCompressedId & OpSpaceCompressedId) | StableId;
|
|
353
437
|
|
|
354
438
|
/**
|
|
355
439
|
* Normalizes a session space ID into op space.
|
|
356
|
-
*
|
|
440
|
+
*
|
|
441
|
+
* @remarks The returned ID is in op space and can be safely serialized.
|
|
357
442
|
* However, it should be {@link IIdCompressor.normalizeToSessionSpace|normalized back to session space} before use.
|
|
443
|
+
*
|
|
358
444
|
* @param id - The local ID to normalize.
|
|
359
445
|
* @returns The ID in op space.
|
|
360
446
|
*/
|
|
@@ -362,23 +448,25 @@ export interface IIdCompressor {
|
|
|
362
448
|
|
|
363
449
|
/**
|
|
364
450
|
* Normalizes an ID into session space.
|
|
365
|
-
*
|
|
366
|
-
* @param originSessionId - The {@link IIdCompressor.localSessionId} of the compressor which {@link IIdCompressor.normalizeToOpSpace|encoded} `id`.
|
|
367
|
-
* This might not be the client that created `id` in the first place, but rather the client that serialized it.
|
|
368
|
-
* This is an important distinction in the case of a reference, where a client might refer to an ID created by another client.
|
|
369
|
-
* @returns The session-space ID in the local session corresponding to `id`.
|
|
451
|
+
*
|
|
370
452
|
* @remarks
|
|
371
453
|
* For this to be valid, `originSessionId` must be the {@link IIdCompressor.localSessionId} of the compressor that normalized `id` to op space.
|
|
372
|
-
* Additionally, this compressor must know about the relevant {@link IdCreationRange} from the encoding
|
|
454
|
+
* Additionally, this compressor must know about the relevant {@link IdCreationRange} from the encoding {@link IIdCompressor}.
|
|
373
455
|
* This can be accomplished using creating the compressor using {@link (deserializeIdCompressor:1)},
|
|
374
456
|
* or relying on something (like the Fluid runtime) to synchronize the range information using internal mechanisms.
|
|
375
457
|
*
|
|
376
|
-
* The {@link IIdCompressor} provided by the Fluid Framework runtime will automatically track {@link IdCreationRange| Id Creation Ranges} (via its own ops) from other clients in the container.
|
|
458
|
+
* The {@link IIdCompressor} provided by the Fluid Framework runtime will automatically track {@link IdCreationRange | Id Creation Ranges} (via its own ops) from other clients in the container.
|
|
377
459
|
* It is up to the user of the {@link IIdCompressor} to ensure the relevant session information is available,
|
|
378
460
|
* either by depending on the originating client's session ID for ops, or by explicitly storing it alongside the data containing the {@link OpSpaceCompressedId}.
|
|
379
461
|
*
|
|
380
462
|
* Note that attachment summaries, while they are ops, are also interpreted as summaries without the context of their originating session,
|
|
381
463
|
* so they should explicitly include session IDs if required to decode them just like regular summaries and exported data.
|
|
464
|
+
*
|
|
465
|
+
* @param id - The ID to normalize.
|
|
466
|
+
* @param originSessionId - The {@link IIdCompressor.localSessionId} of the compressor which {@link IIdCompressor.normalizeToOpSpace|encoded} `id`.
|
|
467
|
+
* This might not be the client that created `id` in the first place, but rather the client that serialized it.
|
|
468
|
+
* This is an important distinction in the case of a reference, where a client might refer to an ID created by another client.
|
|
469
|
+
* @returns The session-space ID in the local session corresponding to `id`.
|
|
382
470
|
*/
|
|
383
471
|
normalizeToSessionSpace(
|
|
384
472
|
id: OpSpaceCompressedId,
|