@fluidframework/id-compressor 3.2.0 → 3.4.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 +15 -0
- package/api-report/id-compressor.legacy.beta.api.md +0 -49
- package/dist/idCompressor.d.ts +65 -22
- package/dist/idCompressor.d.ts.map +1 -1
- package/dist/idCompressor.js +401 -60
- package/dist/idCompressor.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -12
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.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.map +1 -1
- package/dist/test/idCompressorTestUtilities.js +8 -3
- package/dist/test/idCompressorTestUtilities.js.map +1 -1
- package/dist/test/sharding.fuzz.spec.d.ts +6 -0
- package/dist/test/sharding.fuzz.spec.d.ts.map +1 -0
- package/dist/test/sharding.fuzz.spec.js.map +1 -0
- package/dist/test/sharding.spec.d.ts +6 -0
- package/dist/test/sharding.spec.d.ts.map +1 -0
- package/dist/test/sharding.spec.js.map +1 -0
- package/dist/test/snapshots/summary.spec.js.map +1 -1
- package/dist/test/types/validateIdCompressorPrevious.generated.js.map +1 -1
- package/dist/types/idCompressor.d.ts +205 -20
- package/dist/types/idCompressor.d.ts.map +1 -1
- package/dist/types/idCompressor.js +15 -0
- package/dist/types/idCompressor.js.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/persisted-types/0.0.1.d.ts +4 -4
- package/dist/types/persisted-types/0.0.1.js.map +1 -1
- package/dist/utilities.d.ts +1 -1
- package/dist/utilities.js +1 -1
- package/dist/utilities.js.map +1 -1
- package/lib/idCompressor.d.ts +65 -22
- package/lib/idCompressor.d.ts.map +1 -1
- package/lib/idCompressor.js +402 -61
- package/lib/idCompressor.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -12
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.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.map +1 -1
- package/lib/test/idCompressorTestUtilities.js +8 -3
- package/lib/test/idCompressorTestUtilities.js.map +1 -1
- package/lib/test/sharding.fuzz.spec.d.ts +6 -0
- package/lib/test/sharding.fuzz.spec.d.ts.map +1 -0
- package/lib/test/sharding.fuzz.spec.js.map +1 -0
- package/lib/test/sharding.spec.d.ts +6 -0
- package/lib/test/sharding.spec.d.ts.map +1 -0
- package/lib/test/sharding.spec.js.map +1 -0
- package/lib/test/snapshots/summary.spec.js.map +1 -1
- package/lib/test/types/validateIdCompressorPrevious.generated.js.map +1 -1
- package/lib/types/idCompressor.d.ts +205 -20
- package/lib/types/idCompressor.d.ts.map +1 -1
- package/lib/types/idCompressor.js +14 -1
- package/lib/types/idCompressor.js.map +1 -1
- package/lib/types/index.d.ts +2 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -1
- package/lib/types/index.js.map +1 -1
- package/lib/types/persisted-types/0.0.1.d.ts +4 -4
- package/lib/types/persisted-types/0.0.1.js.map +1 -1
- package/lib/utilities.d.ts +1 -1
- package/lib/utilities.js +1 -1
- package/lib/utilities.js.map +1 -1
- package/package.json +8 -8
- package/src/idCompressor.ts +550 -78
- package/src/index.ts +11 -5
- package/src/packageVersion.ts +1 -1
- package/src/test/idCompressorTestUtilities.ts +16 -3
- package/src/test/snapshots/v2/compressor-with-finalized-range-from-one-client +1 -0
- package/src/test/snapshots/v2/compressor-with-finalized-range-from-one-client-client2 +1 -0
- package/src/test/snapshots/v2/compressors-with-finalized-ranges-from-two-clients +1 -0
- package/src/test/snapshots/v2/compressors-with-finalized-ranges-from-two-clients-client2 +1 -0
- package/src/test/snapshots/v2/empty-compressor-summary +1 -0
- package/src/test/snapshots/v2/expansion-semantics +1 -0
- package/src/test/snapshots/v2/expansion-semantics-client2 +1 -0
- package/src/test/snapshots/v3/compressor-with-finalized-range-from-one-client +1 -0
- package/src/test/snapshots/v3/compressor-with-finalized-range-from-one-client-client2 +1 -0
- package/src/test/snapshots/v3/compressors-with-finalized-ranges-from-two-clients +1 -0
- package/src/test/snapshots/v3/compressors-with-finalized-ranges-from-two-clients-client2 +1 -0
- package/src/test/snapshots/v3/empty-compressor-summary +1 -0
- package/src/test/snapshots/v3/expansion-semantics +1 -0
- package/src/test/snapshots/v3/expansion-semantics-client2 +1 -0
- package/src/test/types/validateIdCompressorPrevious.generated.ts +1 -109
- package/src/types/idCompressor.ts +225 -20
- package/src/types/index.ts +13 -1
- package/src/types/persisted-types/0.0.1.ts +4 -4
- package/src/utilities.ts +1 -1
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 {
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
lastFinalizedLocal,
|
|
43
43
|
} from "./sessions.js";
|
|
44
44
|
import type {
|
|
45
|
+
ShardToken,
|
|
45
46
|
IIdCompressor,
|
|
46
47
|
IIdCompressorCore,
|
|
47
48
|
IdCreationRange,
|
|
@@ -52,7 +53,12 @@ import type {
|
|
|
52
53
|
SessionId,
|
|
53
54
|
SessionSpaceCompressedId,
|
|
54
55
|
StableId,
|
|
56
|
+
ShardSynchronizationToken,
|
|
57
|
+
SerializedIdCompressorShard,
|
|
58
|
+
ParentShardSynchronizationToken,
|
|
59
|
+
IdCompressorEvents,
|
|
55
60
|
} from "./types/index.js";
|
|
61
|
+
import { SerializationVersion } from "./types/index.js";
|
|
56
62
|
import {
|
|
57
63
|
createSessionId,
|
|
58
64
|
genCountFromLocalId,
|
|
@@ -63,12 +69,6 @@ import {
|
|
|
63
69
|
subtractNumericUuids,
|
|
64
70
|
} from "./utilities.js";
|
|
65
71
|
|
|
66
|
-
/**
|
|
67
|
-
* The version of IdCompressor that is currently persisted.
|
|
68
|
-
* This should not be changed without careful consideration to compatibility.
|
|
69
|
-
*/
|
|
70
|
-
const currentWrittenVersion = 2;
|
|
71
|
-
|
|
72
72
|
function rangeFinalizationError(expectedStart: number, actualStart: number): LoggingError {
|
|
73
73
|
return new LoggingError("Ranges finalized out of order", {
|
|
74
74
|
expectedStart,
|
|
@@ -76,10 +76,28 @@ function rangeFinalizationError(expectedStart: number, actualStart: number): Log
|
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
const MAX_STRIDE_LENGTH = 1000000;
|
|
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
|
+
|
|
79
93
|
/**
|
|
80
94
|
* See {@link IIdCompressor} and {@link IIdCompressorCore}
|
|
81
95
|
*/
|
|
82
96
|
export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
97
|
+
private readonly eventEmitter = createEmitter<IdCompressorEvents>();
|
|
98
|
+
|
|
99
|
+
public readonly events: Listenable<IdCompressorEvents> = this.eventEmitter;
|
|
100
|
+
|
|
83
101
|
/**
|
|
84
102
|
* Max allowed initial cluster size.
|
|
85
103
|
*/
|
|
@@ -93,13 +111,51 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
93
111
|
// The number of IDs generated by the local session
|
|
94
112
|
private localGenCount = 0;
|
|
95
113
|
|
|
114
|
+
/**
|
|
115
|
+
* The sharding state of this compressor, if it has been sharded or is a shard itself.
|
|
116
|
+
* When defined, this compressor operates in "sharding mode" where:
|
|
117
|
+
* - IDs are generated using stride-based allocation (no eager finals)
|
|
118
|
+
* - The normalizer backfills all IDs in each stride cycle
|
|
119
|
+
* - Normal operation resumes when all children are disposed
|
|
120
|
+
*/
|
|
121
|
+
private shardingState?:
|
|
122
|
+
| {
|
|
123
|
+
/**
|
|
124
|
+
* Current stride - used when this compressor has active children.
|
|
125
|
+
* Changes each time shard() is called.
|
|
126
|
+
*/
|
|
127
|
+
currentStride: number;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Original stride - the stride this compressor was created with.
|
|
131
|
+
* Used when this compressor has no children (is a leaf).
|
|
132
|
+
* IMMUTABLE after creation.
|
|
133
|
+
*/
|
|
134
|
+
originalStride: number;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Set of unique child shard IDs (UUIDs) currently active.
|
|
138
|
+
* Each child is assigned a UUID when created via shard().
|
|
139
|
+
* Safe across serialization/deserialization cycles.
|
|
140
|
+
*/
|
|
141
|
+
activeChildIds: Set<SessionId>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* This compressor's unique shard ID within its parent (if it's a child).
|
|
145
|
+
* Undefined for root compressors.
|
|
146
|
+
* Assigned by parent when creating this shard.
|
|
147
|
+
*/
|
|
148
|
+
shardId?: SessionId;
|
|
149
|
+
}
|
|
150
|
+
| undefined;
|
|
151
|
+
|
|
96
152
|
// #endregion
|
|
97
153
|
|
|
98
154
|
// #region Final state
|
|
99
155
|
|
|
100
156
|
/**
|
|
101
157
|
* The gen count to be annotated on the range returned by the next call to `takeNextCreationRange`.
|
|
102
|
-
* This is advanced to `
|
|
158
|
+
* This is advanced to `localGenCount` + 1 each time it is called.
|
|
103
159
|
* On the other hand, when `resetUnfinalizedCreationRange` is called,
|
|
104
160
|
* this is moved back to the start of the unfinalized range, to ensure those IDs are included in the next range.
|
|
105
161
|
*/
|
|
@@ -131,6 +187,12 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
131
187
|
public constructor(
|
|
132
188
|
localSessionIdOrDeserialized: SessionId | Sessions,
|
|
133
189
|
private readonly logger: TelemetryLoggerExt | undefined,
|
|
190
|
+
/**
|
|
191
|
+
* The version this compressor will write out.
|
|
192
|
+
* This applies to both `serialize` and creation range functions.
|
|
193
|
+
* This number also determines what features are available (e.g. sharding support starts at V3).
|
|
194
|
+
*/
|
|
195
|
+
private readonly writeVersion: SerializationVersion,
|
|
134
196
|
) {
|
|
135
197
|
if (typeof localSessionIdOrDeserialized === "string") {
|
|
136
198
|
this.localSessionId = localSessionIdOrDeserialized;
|
|
@@ -164,30 +226,42 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
164
226
|
return lastFinalizedFinal(
|
|
165
227
|
this.ongoingGhostSession.cluster,
|
|
166
228
|
) as unknown as SessionSpaceCompressedId;
|
|
167
|
-
}
|
|
168
|
-
this.localGenCount++;
|
|
169
|
-
const lastCluster = this.localSession.getLastCluster();
|
|
170
|
-
if (lastCluster === undefined) {
|
|
171
|
-
this.telemetryLocalIdCount++;
|
|
172
|
-
return this.generateNextLocalId();
|
|
173
|
-
}
|
|
229
|
+
}
|
|
174
230
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
231
|
+
// In sharding mode, always generate local IDs with stride-based allocation.
|
|
232
|
+
// Eager finals are disabled because shards backfill IDs not in their stride cycle.
|
|
233
|
+
// If another shard generated them as eager finals, ingesting IDs from another shard
|
|
234
|
+
// would fail depending on timing.
|
|
235
|
+
if (this.shardingState !== undefined) {
|
|
236
|
+
this.telemetryLocalIdCount++;
|
|
237
|
+
const { currentStride } = this.shardingState;
|
|
238
|
+
this.normalizer.addLocalRange(this.localGenCount + 1, currentStride);
|
|
239
|
+
this.localGenCount += currentStride;
|
|
240
|
+
return localIdFromGenCount(this.localGenCount);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Normal ID generation
|
|
244
|
+
this.localGenCount++;
|
|
245
|
+
const lastCluster = this.localSession.getLastCluster();
|
|
246
|
+
if (lastCluster === undefined) {
|
|
188
247
|
this.telemetryLocalIdCount++;
|
|
189
248
|
return this.generateNextLocalId();
|
|
190
249
|
}
|
|
250
|
+
|
|
251
|
+
// If there exists a cluster of final IDs already claimed by the local session that still has room in it,
|
|
252
|
+
// it is known prior to range sequencing what a local ID's corresponding final ID will be.
|
|
253
|
+
// In this case, it is safe to return the final ID immediately. This is guaranteed to be safe because
|
|
254
|
+
// any op that the local session sends that contains one of those final IDs are guaranteed to arrive to
|
|
255
|
+
// collaborators *after* the one containing the creation range.
|
|
256
|
+
const clusterOffset = this.localGenCount - genCountFromLocalId(lastCluster.baseLocalId);
|
|
257
|
+
if (lastCluster.capacity > clusterOffset) {
|
|
258
|
+
this.telemetryEagerFinalIdCount++;
|
|
259
|
+
// Space in the cluster: eager final
|
|
260
|
+
return ((lastCluster.baseFinalId as number) + clusterOffset) as SessionSpaceCompressedId;
|
|
261
|
+
}
|
|
262
|
+
// No space in the cluster, return next local
|
|
263
|
+
this.telemetryLocalIdCount++;
|
|
264
|
+
return this.generateNextLocalId();
|
|
191
265
|
}
|
|
192
266
|
|
|
193
267
|
public generateDocumentUniqueId():
|
|
@@ -206,10 +280,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
206
280
|
this.ongoingGhostSession = { ghostSessionId };
|
|
207
281
|
}
|
|
208
282
|
|
|
209
|
-
/**
|
|
210
|
-
* {@inheritdoc IIdCompressorCore.beginGhostSession}
|
|
211
|
-
*/
|
|
212
283
|
public beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void): void {
|
|
284
|
+
if (this.shardingState) {
|
|
285
|
+
throw new Error("Cannot start a ghost session while sharded.");
|
|
286
|
+
}
|
|
213
287
|
this.startGhostSession(ghostSessionId);
|
|
214
288
|
try {
|
|
215
289
|
ghostSessionCallback();
|
|
@@ -218,6 +292,233 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
218
292
|
}
|
|
219
293
|
}
|
|
220
294
|
|
|
295
|
+
public shard(newShardCount: number): SerializedIdCompressorShard[] {
|
|
296
|
+
if (!Number.isSafeInteger(newShardCount) || newShardCount <= 0) {
|
|
297
|
+
throw new Error("Shard count must be a positive safe integer");
|
|
298
|
+
}
|
|
299
|
+
if (this.ongoingGhostSession) {
|
|
300
|
+
throw new Error("Cannot shard during ghost session");
|
|
301
|
+
}
|
|
302
|
+
if (this.writeVersion < SerializationVersion.V3) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`Sharding requires document version ${SerializationVersion.V3} or higher, but this document is version ${this.writeVersion}`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const currentStride = this.shardingState?.currentStride ?? 1;
|
|
309
|
+
const newStride = currentStride * (newShardCount + 1);
|
|
310
|
+
|
|
311
|
+
// ID usage is exponential in stride count. Limit to practical size to avoid overflow.
|
|
312
|
+
if (newStride > MAX_STRIDE_LENGTH) {
|
|
313
|
+
throw new Error("Sharding limit reached.");
|
|
314
|
+
}
|
|
315
|
+
assert(Number.isSafeInteger(newStride), 0xd52 /* Shard stride must be a safe integer */);
|
|
316
|
+
|
|
317
|
+
if (this.shardingState === undefined) {
|
|
318
|
+
// First time sharding - initialize state
|
|
319
|
+
this.shardingState = {
|
|
320
|
+
currentStride: newStride,
|
|
321
|
+
originalStride: 1,
|
|
322
|
+
activeChildIds: new Set(),
|
|
323
|
+
};
|
|
324
|
+
} else {
|
|
325
|
+
this.shardingState.currentStride = newStride;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Cache to avoid repeating per child
|
|
329
|
+
const serialized = this.serialize(true);
|
|
330
|
+
const { localGenCount: parentGenCount } = this;
|
|
331
|
+
|
|
332
|
+
const shards: SerializedIdCompressorShard[] = [];
|
|
333
|
+
|
|
334
|
+
// Children are positioned at consecutive positions within the parents current stride cycle
|
|
335
|
+
// e.g. if parent owns the "evens" space (-2, -4, -6, ...) and is sharded into 3 (two children)
|
|
336
|
+
// while at -8, then the child shards will start at -10 and -12 with a stride of 6
|
|
337
|
+
for (let i = 1; i <= newShardCount; i++) {
|
|
338
|
+
const childShardId = createSessionId();
|
|
339
|
+
this.shardingState.activeChildIds.add(childShardId);
|
|
340
|
+
|
|
341
|
+
const childLocalGenCount = parentGenCount + i * currentStride;
|
|
342
|
+
|
|
343
|
+
// This code results in a double roundtrip, but is probably plenty fast enough for scenarios that need sharding,
|
|
344
|
+
// as they necessarily have process boundaries to deal with.
|
|
345
|
+
// If this ever became an issue we can implement a fast clone of compressors
|
|
346
|
+
const child = deserializeIdCompressor(serialized, this.writeVersion) as IdCompressor;
|
|
347
|
+
const genCountJump = childLocalGenCount - parentGenCount;
|
|
348
|
+
assert(
|
|
349
|
+
child.localGenCount === parentGenCount && genCountJump > 0,
|
|
350
|
+
0xd53 /* Child offsets incorrectly calculated. */,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
child.normalizer.addLocalRange(child.localGenCount + 1, genCountJump);
|
|
354
|
+
child.localGenCount = childLocalGenCount;
|
|
355
|
+
child.shardingState = {
|
|
356
|
+
currentStride: newStride,
|
|
357
|
+
originalStride: newStride,
|
|
358
|
+
activeChildIds: new Set(),
|
|
359
|
+
shardId: childShardId,
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
shards.push({
|
|
363
|
+
serialized: child.serialize(true),
|
|
364
|
+
syncToken: child.makeShardToken(false),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return shards;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
public synchronizeWithShard(syncToken: ShardSynchronizationToken): void {
|
|
372
|
+
const isNowLeaf = this.synchronizeChild(syncToken);
|
|
373
|
+
// A disposal token additionally reclaims the child's ID space. If reclaiming the last child
|
|
374
|
+
// returns this compressor to a leaf and it is the root (originalStride === 1), exit sharding mode.
|
|
375
|
+
if (syncToken.disposed) {
|
|
376
|
+
assert(this.shardingState !== undefined, 0xd54 /* Must be sharded */);
|
|
377
|
+
if (isNowLeaf && this.shardingState.originalStride === 1) {
|
|
378
|
+
this.shardingState = undefined;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
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
|
+
|
|
431
|
+
private synchronizeChild(syncToken: ShardToken): boolean {
|
|
432
|
+
const childDisposed = syncToken.disposed;
|
|
433
|
+
if (this.writeVersion < SerializationVersion.V3) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
`Sharding requires document version ${SerializationVersion.V3} or higher, but this document is version ${this.writeVersion}`,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
if (this.shardingState === undefined) {
|
|
439
|
+
throw new Error("Must be in sharding mode.");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const childShardId = syncToken.shardId;
|
|
443
|
+
const { activeChildIds, originalStride } = this.shardingState;
|
|
444
|
+
|
|
445
|
+
// Verify this child belongs to us
|
|
446
|
+
if (!activeChildIds.has(childShardId)) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Cannot synchronize with child with ID ${childShardId}: not in active children set`,
|
|
449
|
+
);
|
|
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
|
+
);
|
|
460
|
+
|
|
461
|
+
// A plain synchronization leaves the child active so it can keep generating IDs.
|
|
462
|
+
if (childDisposed) {
|
|
463
|
+
activeChildIds.delete(childShardId);
|
|
464
|
+
if (isLeaf) {
|
|
465
|
+
this.shardingState.currentStride = originalStride;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return isLeaf;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
public disposeShard(): ShardSynchronizationToken | undefined {
|
|
473
|
+
if (this.shardingState === undefined) {
|
|
474
|
+
return undefined;
|
|
475
|
+
} else {
|
|
476
|
+
if (this.shardingState.activeChildIds.size > 0) {
|
|
477
|
+
throw new Error("Cannot dispose a shard with active children.");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// A disposal token is a sync token that additionally signals reclamation of this shard's ID space.
|
|
482
|
+
const token = this.makeShardToken(true);
|
|
483
|
+
|
|
484
|
+
// Make this compressor unusable by replacing all methods with throwing stubs
|
|
485
|
+
// This is a one-time mutation that avoids runtime checks in every method
|
|
486
|
+
makeCompressorUnusable(this);
|
|
487
|
+
|
|
488
|
+
return token;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
public getShardSyncToken(): ShardSynchronizationToken | undefined {
|
|
492
|
+
if (this.shardingState === undefined) {
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return this.makeShardToken(false);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Builds a token describing this shard's current progress.
|
|
501
|
+
* @param disposed - Whether the token also signals disposal (reclamation of this shard's ID space).
|
|
502
|
+
*/
|
|
503
|
+
private makeShardToken(disposed: boolean): ShardSynchronizationToken {
|
|
504
|
+
assert(this.shardingState !== undefined, 0xd55 /* Compressor is not sharded. */);
|
|
505
|
+
|
|
506
|
+
// Root cannot produce a token (shardId is undefined for root)
|
|
507
|
+
if (this.shardingState.shardId === undefined) {
|
|
508
|
+
throw new Error(
|
|
509
|
+
"Cannot get a token for a root compressor - only shards can be disposed",
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const token: ShardToken = {
|
|
514
|
+
localGenCount: this.localGenCount,
|
|
515
|
+
shardId: this.shardingState.shardId,
|
|
516
|
+
disposed,
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
return token as unknown as ShardSynchronizationToken;
|
|
520
|
+
}
|
|
521
|
+
|
|
221
522
|
private generateNextLocalId(): LocalCompressedId {
|
|
222
523
|
// Must tell the normalizer that we generated a local ID
|
|
223
524
|
this.normalizer.addLocalRange(this.localGenCount, 1);
|
|
@@ -225,6 +526,14 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
225
526
|
}
|
|
226
527
|
|
|
227
528
|
public takeNextCreationRange(): IdCreationRange {
|
|
529
|
+
// Child (non-root) shards must not submit their own creation ranges. All shards in a shard tree share a
|
|
530
|
+
// single session, and only the root shard is responsible for finalizing that session's IDs with the server.
|
|
531
|
+
// A child's generated IDs are reconciled with the root via shard synchronization tokens (see
|
|
532
|
+
// synchronizeWithShard / disposeShard), not by independently sending ranges, which would overlap and
|
|
533
|
+
// conflict with the root's ranges for the shared session.
|
|
534
|
+
if (this.shardingState?.shardId !== undefined) {
|
|
535
|
+
throw new Error("Cannot take a creation range from a non-root shard.");
|
|
536
|
+
}
|
|
228
537
|
assert(
|
|
229
538
|
!this.ongoingGhostSession,
|
|
230
539
|
0x8a6 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
@@ -377,6 +686,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
377
686
|
}
|
|
378
687
|
|
|
379
688
|
assert(!session.isEmpty(), 0x757 /* Empty sessions should not be created. */);
|
|
689
|
+
this.eventEmitter.emit("rangeFinalized", range);
|
|
380
690
|
}
|
|
381
691
|
|
|
382
692
|
private addEmptyCluster(session: Session, capacity: number): IdCluster {
|
|
@@ -577,11 +887,24 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
577
887
|
sessionIndex++;
|
|
578
888
|
}
|
|
579
889
|
}
|
|
890
|
+
const sizeOfUuid = 2;
|
|
891
|
+
const shardingStateSize =
|
|
892
|
+
hasLocalState && this.writeVersion >= SerializationVersion.V3
|
|
893
|
+
? this.shardingState === undefined
|
|
894
|
+
? 1 // Boolean: no sharding state
|
|
895
|
+
: 1 + // Boolean: has sharding state
|
|
896
|
+
2 + // Numbers: currentStride, originalStride (Float64)
|
|
897
|
+
1 + // Number: activeChildIds count (Float64)
|
|
898
|
+
this.shardingState.activeChildIds.size * sizeOfUuid + // UUIDs: each is 128-bit
|
|
899
|
+
1 + // Boolean: hasShardId
|
|
900
|
+
(this.shardingState.shardId === undefined ? 0 : sizeOfUuid) // UUID: 128-bit if present
|
|
901
|
+
: 0;
|
|
580
902
|
const localStateSize = hasLocalState
|
|
581
903
|
? 1 + // generated ID count
|
|
582
904
|
1 + // next range base genCount
|
|
583
905
|
1 + // count of normalizer pairs
|
|
584
|
-
normalizer.idRanges.size * 2 // pairs
|
|
906
|
+
normalizer.idRanges.size * 2 + // pairs
|
|
907
|
+
shardingStateSize // sharding state if present
|
|
585
908
|
: 0;
|
|
586
909
|
// Layout size, in 8 byte increments
|
|
587
910
|
const totalSize =
|
|
@@ -589,14 +912,14 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
589
912
|
1 + // hasLocalState
|
|
590
913
|
1 + // session count
|
|
591
914
|
1 + // cluster count
|
|
592
|
-
sessionIndexMap.size *
|
|
915
|
+
sessionIndexMap.size * sizeOfUuid + // session IDs
|
|
593
916
|
finalSpace.clusters.length * 3 + // clusters: (sessionIndex, capacity, count)[]
|
|
594
917
|
localStateSize; // local state, if present
|
|
595
918
|
|
|
596
919
|
const serializedFloat = new Float64Array(totalSize);
|
|
597
920
|
const serializedUint = new BigUint64Array(serializedFloat.buffer);
|
|
598
921
|
let index = 0;
|
|
599
|
-
index = writeNumber(serializedFloat, index,
|
|
922
|
+
index = writeNumber(serializedFloat, index, this.writeVersion);
|
|
600
923
|
index = writeBoolean(serializedFloat, index, hasLocalState);
|
|
601
924
|
index = writeNumber(serializedFloat, index, sessionIndexMap.size);
|
|
602
925
|
index = writeNumber(serializedFloat, index, finalSpace.clusters.length);
|
|
@@ -623,6 +946,35 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
623
946
|
index = writeNumber(serializedFloat, index, leadingGenCount);
|
|
624
947
|
index = writeNumber(serializedFloat, index, count);
|
|
625
948
|
}
|
|
949
|
+
|
|
950
|
+
// Write sharding state (version 3+)
|
|
951
|
+
if (this.writeVersion >= SerializationVersion.V3) {
|
|
952
|
+
index = writeBoolean(serializedFloat, index, this.shardingState !== undefined);
|
|
953
|
+
if (this.shardingState !== undefined) {
|
|
954
|
+
index = writeNumber(serializedFloat, index, this.shardingState.currentStride);
|
|
955
|
+
index = writeNumber(serializedFloat, index, this.shardingState.originalStride);
|
|
956
|
+
|
|
957
|
+
// Write activeChildIds Set as count + array of NumericUuids
|
|
958
|
+
index = writeNumber(serializedFloat, index, this.shardingState.activeChildIds.size);
|
|
959
|
+
for (const childId of this.shardingState.activeChildIds) {
|
|
960
|
+
index = writeNumericUuid(serializedUint, index, numericUuidFromStableId(childId));
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Write shardId (optional)
|
|
964
|
+
index = writeBoolean(
|
|
965
|
+
serializedFloat,
|
|
966
|
+
index,
|
|
967
|
+
this.shardingState.shardId !== undefined,
|
|
968
|
+
);
|
|
969
|
+
if (this.shardingState.shardId !== undefined) {
|
|
970
|
+
index = writeNumericUuid(
|
|
971
|
+
serializedUint,
|
|
972
|
+
index,
|
|
973
|
+
numericUuidFromStableId(this.shardingState.shardId),
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
626
978
|
}
|
|
627
979
|
|
|
628
980
|
assert(index === totalSize, 0x75b /* Serialized size was incorrectly calculated. */);
|
|
@@ -640,41 +992,45 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
640
992
|
params:
|
|
641
993
|
| {
|
|
642
994
|
serialized: SerializedIdCompressorWithOngoingSession;
|
|
995
|
+
requestedWriteVersion: SerializationVersion;
|
|
643
996
|
logger?: TelemetryLoggerExt | undefined;
|
|
644
997
|
newSessionId?: never;
|
|
645
998
|
}
|
|
646
999
|
| {
|
|
647
1000
|
serialized: SerializedIdCompressorWithNoSession;
|
|
648
1001
|
newSessionId: SessionId;
|
|
1002
|
+
requestedWriteVersion: SerializationVersion;
|
|
649
1003
|
logger?: TelemetryLoggerExt | undefined;
|
|
650
1004
|
},
|
|
651
1005
|
): IdCompressor {
|
|
652
|
-
const { serialized, newSessionId, logger } = params;
|
|
1006
|
+
const { serialized, newSessionId: sessionId, logger, requestedWriteVersion } = params;
|
|
653
1007
|
const buffer = stringToBuffer(serialized, "base64");
|
|
654
1008
|
const index: Index = {
|
|
655
1009
|
index: 0,
|
|
656
1010
|
bufferFloat: new Float64Array(buffer),
|
|
657
1011
|
bufferUint: new BigUint64Array(buffer),
|
|
658
1012
|
};
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1013
|
+
const serializedVersion = readNumber(index) as SerializationVersion;
|
|
1014
|
+
// If requested version is < the serialized version, we must write the version serialized
|
|
1015
|
+
// to avoid losing data.
|
|
1016
|
+
const writeVersion = Math.max(
|
|
1017
|
+
requestedWriteVersion,
|
|
1018
|
+
serializedVersion,
|
|
1019
|
+
) as SerializationVersion;
|
|
1020
|
+
|
|
1021
|
+
switch (serializedVersion) {
|
|
1022
|
+
case 2:
|
|
1023
|
+
case 3: {
|
|
1024
|
+
break;
|
|
666
1025
|
}
|
|
667
1026
|
default: {
|
|
1027
|
+
if (serializedVersion === 1) {
|
|
1028
|
+
throw new Error("IdCompressor version 1.0 is no longer supported.");
|
|
1029
|
+
}
|
|
668
1030
|
throw new Error("Unknown IdCompressor serialized version.");
|
|
669
1031
|
}
|
|
670
1032
|
}
|
|
671
|
-
}
|
|
672
1033
|
|
|
673
|
-
static deserialize2_0(
|
|
674
|
-
index: Index,
|
|
675
|
-
sessionId: SessionId | undefined,
|
|
676
|
-
logger: TelemetryLoggerExt | undefined,
|
|
677
|
-
): IdCompressor {
|
|
678
1034
|
const hasLocalState = readBoolean(index);
|
|
679
1035
|
const sessionCount = readNumber(index);
|
|
680
1036
|
const clusterCount = readNumber(index);
|
|
@@ -700,7 +1056,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
700
1056
|
sessions.push([numeric, new Session(numeric)]);
|
|
701
1057
|
}
|
|
702
1058
|
|
|
703
|
-
const compressor = new IdCompressor(new Sessions(sessions), logger);
|
|
1059
|
+
const compressor = new IdCompressor(new Sessions(sessions), logger, writeVersion);
|
|
704
1060
|
|
|
705
1061
|
// Clusters
|
|
706
1062
|
let baseFinalId = 0;
|
|
@@ -727,6 +1083,38 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
727
1083
|
for (let i = 0; i < normalizerCount; i++) {
|
|
728
1084
|
compressor.normalizer.addLocalRange(readNumber(index), readNumber(index));
|
|
729
1085
|
}
|
|
1086
|
+
|
|
1087
|
+
// Sharding state (version 3+)
|
|
1088
|
+
if (serializedVersion >= 3) {
|
|
1089
|
+
const hasShardingState = readBoolean(index);
|
|
1090
|
+
if (hasShardingState) {
|
|
1091
|
+
const currentStride = readNumber(index);
|
|
1092
|
+
const originalStride = readNumber(index);
|
|
1093
|
+
|
|
1094
|
+
// Read activeChildIds Set
|
|
1095
|
+
const activeChildCount = readNumber(index);
|
|
1096
|
+
const activeChildIds = new Set<SessionId>();
|
|
1097
|
+
for (let i = 0; i < activeChildCount; i++) {
|
|
1098
|
+
const childIdNumeric = readNumericUuid(index);
|
|
1099
|
+
// Safe cast: these were serialized as SessionIds
|
|
1100
|
+
activeChildIds.add(stableIdFromNumericUuid(childIdNumeric) as SessionId);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Read shardId (optional)
|
|
1104
|
+
const hasShardId = readBoolean(index);
|
|
1105
|
+
const shardId: SessionId | undefined = hasShardId
|
|
1106
|
+
? (stableIdFromNumericUuid(readNumericUuid(index)) as SessionId)
|
|
1107
|
+
: undefined;
|
|
1108
|
+
|
|
1109
|
+
// Build sharding state object, conditionally including shardId
|
|
1110
|
+
compressor.shardingState = {
|
|
1111
|
+
currentStride,
|
|
1112
|
+
originalStride,
|
|
1113
|
+
activeChildIds,
|
|
1114
|
+
...(shardId !== undefined && { shardId }),
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
730
1118
|
}
|
|
731
1119
|
|
|
732
1120
|
assert(
|
|
@@ -747,6 +1135,34 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
747
1135
|
) {
|
|
748
1136
|
return false;
|
|
749
1137
|
}
|
|
1138
|
+
// Shard ownership affects future local ID allocation, even when the generated IDs match.
|
|
1139
|
+
// It is not part of finalized state, so ignore it when comparing only that state.
|
|
1140
|
+
if (includeLocalState) {
|
|
1141
|
+
const state = this.shardingState;
|
|
1142
|
+
const otherState = other.shardingState;
|
|
1143
|
+
if (state === undefined || otherState === undefined) {
|
|
1144
|
+
// Two unsharded compressors match here; a sharded and an unsharded compressor do not.
|
|
1145
|
+
if (state !== otherState) {
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1148
|
+
} else {
|
|
1149
|
+
if (
|
|
1150
|
+
state.currentStride !== otherState.currentStride ||
|
|
1151
|
+
state.originalStride !== otherState.originalStride ||
|
|
1152
|
+
state.shardId !== otherState.shardId ||
|
|
1153
|
+
state.activeChildIds.size !== otherState.activeChildIds.size
|
|
1154
|
+
) {
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
// Child ownership is an unordered set. Equal sizes and membership imply equal sets,
|
|
1158
|
+
// regardless of insertion order or whether the state was deserialized.
|
|
1159
|
+
for (const childId of state.activeChildIds) {
|
|
1160
|
+
if (!otherState.activeChildIds.has(childId)) {
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
750
1166
|
return (
|
|
751
1167
|
this.sessions.equals(other.sessions, includeLocalState) &&
|
|
752
1168
|
this.finalSpace.equals(other.finalSpace)
|
|
@@ -756,73 +1172,94 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
756
1172
|
|
|
757
1173
|
/**
|
|
758
1174
|
* Create a new {@link IIdCompressor}.
|
|
759
|
-
*
|
|
760
|
-
* @
|
|
1175
|
+
* @param writeVersion - The version the compressor will write when serializing. Use SerializationVersion.V2 for base format, or SerializationVersion.V3 for sharding support.
|
|
1176
|
+
* @param logger - Optional telemetry logger.
|
|
1177
|
+
* @internal
|
|
761
1178
|
*/
|
|
762
|
-
export function createIdCompressor(
|
|
1179
|
+
export function createIdCompressor(
|
|
1180
|
+
writeVersion: SerializationVersion,
|
|
1181
|
+
logger?: ITelemetryBaseLogger,
|
|
1182
|
+
): IIdCompressor & IIdCompressorCore;
|
|
763
1183
|
/**
|
|
764
1184
|
* Create a new {@link IIdCompressor}.
|
|
765
1185
|
* @param sessionId - The seed ID for the compressor.
|
|
766
|
-
*
|
|
767
|
-
* @
|
|
1186
|
+
* @param writeVersion - The version the compressor will write when serializing. Use SerializationVersion.V2 for base format, or SerializationVersion.V3 for sharding support.
|
|
1187
|
+
* @param logger - Optional telemetry logger.
|
|
1188
|
+
* @internal
|
|
768
1189
|
*/
|
|
769
1190
|
export function createIdCompressor(
|
|
770
1191
|
sessionId: SessionId,
|
|
1192
|
+
writeVersion: SerializationVersion,
|
|
771
1193
|
logger?: ITelemetryBaseLogger,
|
|
772
|
-
): IIdCompressor;
|
|
1194
|
+
): IIdCompressor & IIdCompressorCore;
|
|
773
1195
|
export function createIdCompressor(
|
|
774
|
-
|
|
1196
|
+
sessionIdOrDocumentVersion: SessionId | SerializationVersion,
|
|
1197
|
+
writeVersionOrLogger?: SerializationVersion | ITelemetryBaseLogger,
|
|
775
1198
|
loggerOrUndefined?: ITelemetryBaseLogger,
|
|
776
1199
|
): IIdCompressor & IIdCompressorCore {
|
|
777
1200
|
let localSessionId: SessionId;
|
|
1201
|
+
let writeVersion: SerializationVersion;
|
|
778
1202
|
let logger: ITelemetryBaseLogger | undefined;
|
|
779
|
-
|
|
780
|
-
|
|
1203
|
+
|
|
1204
|
+
if (typeof sessionIdOrDocumentVersion === "string") {
|
|
1205
|
+
// Called with sessionId, writeVersion, logger?
|
|
1206
|
+
localSessionId = sessionIdOrDocumentVersion;
|
|
1207
|
+
writeVersion = writeVersionOrLogger as SerializationVersion;
|
|
1208
|
+
logger = loggerOrUndefined;
|
|
781
1209
|
} else {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
localSessionId = createSessionId();
|
|
787
|
-
logger = sessionIdOrLogger;
|
|
788
|
-
}
|
|
1210
|
+
// Called with writeVersion, logger?
|
|
1211
|
+
localSessionId = createSessionId();
|
|
1212
|
+
writeVersion = sessionIdOrDocumentVersion;
|
|
1213
|
+
logger = writeVersionOrLogger as ITelemetryBaseLogger | undefined;
|
|
789
1214
|
}
|
|
1215
|
+
|
|
790
1216
|
const compressor = new IdCompressor(
|
|
791
1217
|
localSessionId,
|
|
792
1218
|
logger === undefined ? undefined : createChildLogger({ logger }),
|
|
1219
|
+
writeVersion,
|
|
793
1220
|
);
|
|
794
1221
|
return compressor;
|
|
795
1222
|
}
|
|
796
1223
|
|
|
797
1224
|
/**
|
|
798
1225
|
* Deserializes the supplied state into an ID compressor.
|
|
799
|
-
*
|
|
800
|
-
* @
|
|
1226
|
+
* @param serialized - The serialized compressor state with an ongoing session.
|
|
1227
|
+
* @param writeVersion - The version the compressor will write when serializing. This determines what features are available (e.g., V3 enables sharding support).
|
|
1228
|
+
* @param logger - Optional telemetry logger.
|
|
1229
|
+
* @internal
|
|
801
1230
|
*/
|
|
802
1231
|
export function deserializeIdCompressor(
|
|
803
1232
|
serialized: SerializedIdCompressorWithOngoingSession,
|
|
1233
|
+
writeVersion: SerializationVersion,
|
|
804
1234
|
logger?: ITelemetryLoggerExt,
|
|
805
|
-
): IIdCompressor;
|
|
1235
|
+
): IIdCompressor & IIdCompressorCore;
|
|
806
1236
|
/**
|
|
807
1237
|
* Deserializes the supplied state into an ID compressor.
|
|
808
|
-
*
|
|
809
|
-
* @
|
|
1238
|
+
* @param serialized - The serialized compressor state without a session.
|
|
1239
|
+
* @param newSessionId - The session ID to use for the deserialized compressor.
|
|
1240
|
+
* @param writeVersion - The version the compressor will write when serializing. This determines what features are available (e.g., V3 enables sharding support).
|
|
1241
|
+
* @param logger - Optional telemetry logger.
|
|
1242
|
+
* @internal
|
|
810
1243
|
*/
|
|
811
1244
|
export function deserializeIdCompressor(
|
|
812
1245
|
serialized: SerializedIdCompressorWithNoSession,
|
|
813
1246
|
newSessionId: SessionId,
|
|
1247
|
+
writeVersion: SerializationVersion,
|
|
814
1248
|
logger?: ITelemetryLoggerExt,
|
|
815
|
-
): IIdCompressor;
|
|
1249
|
+
): IIdCompressor & IIdCompressorCore;
|
|
816
1250
|
export function deserializeIdCompressor(
|
|
817
1251
|
serialized: SerializedIdCompressor | SerializedIdCompressorWithNoSession,
|
|
818
|
-
|
|
1252
|
+
sessionIdOrDocumentVersion: SessionId | SerializationVersion,
|
|
1253
|
+
writeVersionOrLogger?: SerializationVersion | ITelemetryLoggerExt,
|
|
819
1254
|
loggerOrUndefined?: ITelemetryLoggerExt,
|
|
820
1255
|
): IIdCompressor & IIdCompressorCore {
|
|
821
|
-
if (typeof
|
|
1256
|
+
if (typeof sessionIdOrDocumentVersion === "string") {
|
|
1257
|
+
// Called with (serialized, sessionId, writeVersion, logger?)
|
|
822
1258
|
return IdCompressor.deserialize({
|
|
823
1259
|
serialized: serialized as SerializedIdCompressorWithNoSession,
|
|
824
1260
|
logger: extractTelemetryLoggerExt<{ PossiblyUndefined: true }>(loggerOrUndefined),
|
|
825
|
-
newSessionId:
|
|
1261
|
+
newSessionId: sessionIdOrDocumentVersion,
|
|
1262
|
+
requestedWriteVersion: writeVersionOrLogger as SerializationVersion,
|
|
826
1263
|
});
|
|
827
1264
|
}
|
|
828
1265
|
|
|
@@ -830,18 +1267,53 @@ export function deserializeIdCompressor(
|
|
|
830
1267
|
loggerOrUndefined === undefined,
|
|
831
1268
|
0xc2d /* logger would be in sessionIdOrLogger in this codepath */,
|
|
832
1269
|
);
|
|
1270
|
+
// Called with (serialized, writeVersion, logger?)
|
|
833
1271
|
return IdCompressor.deserialize({
|
|
834
1272
|
serialized: serialized as SerializedIdCompressorWithOngoingSession,
|
|
835
|
-
logger: extractTelemetryLoggerExt<{ PossiblyUndefined: true }>(
|
|
1273
|
+
logger: extractTelemetryLoggerExt<{ PossiblyUndefined: true }>(
|
|
1274
|
+
writeVersionOrLogger as ITelemetryLoggerExt | undefined,
|
|
1275
|
+
),
|
|
1276
|
+
requestedWriteVersion: sessionIdOrDocumentVersion,
|
|
836
1277
|
});
|
|
837
1278
|
}
|
|
838
1279
|
|
|
1280
|
+
/**
|
|
1281
|
+
* Throws an error indicating the compressor has been disposed.
|
|
1282
|
+
*/
|
|
1283
|
+
const throwDisposed = (): never => {
|
|
1284
|
+
throw new Error(
|
|
1285
|
+
"This compressor has been disposed via disposeShard() and can no longer be used.",
|
|
1286
|
+
);
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Makes an IdCompressor instance unusable by replacing all its methods with functions that throw.
|
|
1291
|
+
* This is called once when disposeShard() is invoked to make the compressor permanently unusable.
|
|
1292
|
+
* @param compressor - The compressor instance to make unusable
|
|
1293
|
+
*/
|
|
1294
|
+
function makeCompressorUnusable(compressor: IdCompressor): void {
|
|
1295
|
+
// Get all method names from the prototype (where class methods live)
|
|
1296
|
+
const proto = Object.getPrototypeOf(compressor) as object;
|
|
1297
|
+
for (const key of Reflect.ownKeys(proto)) {
|
|
1298
|
+
if (key !== "constructor" && typeof Reflect.get(proto, key) === "function") {
|
|
1299
|
+
// Shadow the method only on this instance so other compressors remain usable.
|
|
1300
|
+
// Omitting `enumerable` keeps new overrides non-enumerable, like class methods.
|
|
1301
|
+
// Reflection supports string and symbol keys without adding an index signature.
|
|
1302
|
+
Object.defineProperty(compressor, key, {
|
|
1303
|
+
value: throwDisposed,
|
|
1304
|
+
configurable: true,
|
|
1305
|
+
writable: true,
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
839
1311
|
/**
|
|
840
1312
|
* Serializes an ID compressor.
|
|
841
1313
|
* @param compressor - The compressor to serialize.
|
|
842
1314
|
* @param withSession - If true, the serialized state will include local session
|
|
843
1315
|
* state (for stashing). If false, only finalized state is included (for summaries).
|
|
844
|
-
* @
|
|
1316
|
+
* @internal
|
|
845
1317
|
*/
|
|
846
1318
|
export function serializeIdCompressor(
|
|
847
1319
|
compressor: IIdCompressor,
|
|
@@ -852,7 +1324,7 @@ export function serializeIdCompressor(
|
|
|
852
1324
|
* @param compressor - The compressor to serialize.
|
|
853
1325
|
* @param withSession - If true, the serialized state will include local session
|
|
854
1326
|
* state (for stashing). If false, only finalized state is included (for summaries).
|
|
855
|
-
* @
|
|
1327
|
+
* @internal
|
|
856
1328
|
*/
|
|
857
1329
|
export function serializeIdCompressor(
|
|
858
1330
|
compressor: IIdCompressor,
|