@fluidframework/id-compressor 3.2.0 → 3.3.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 +11 -0
- package/api-report/id-compressor.legacy.beta.api.md +0 -49
- package/dist/idCompressor.d.ts +69 -19
- package/dist/idCompressor.d.ts.map +1 -1
- package/dist/idCompressor.js +365 -57
- 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 +108 -2
- 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 +69 -19
- package/lib/idCompressor.d.ts.map +1 -1
- package/lib/idCompressor.js +365 -57
- 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 +108 -2
- 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 +42 -9
- package/src/idCompressor.ts +497 -73
- package/src/index.ts +3 -0
- 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 +13 -1
- package/src/types/idCompressor.ts +119 -2
- package/src/types/index.ts +10 -1
- package/src/types/persisted-types/0.0.1.ts +4 -4
- package/src/utilities.ts +1 -1
package/src/idCompressor.ts
CHANGED
|
@@ -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,9 @@ import type {
|
|
|
52
53
|
SessionId,
|
|
53
54
|
SessionSpaceCompressedId,
|
|
54
55
|
StableId,
|
|
56
|
+
ShardSynchronizationToken,
|
|
55
57
|
} from "./types/index.js";
|
|
58
|
+
import { SerializationVersion } from "./types/index.js";
|
|
56
59
|
import {
|
|
57
60
|
createSessionId,
|
|
58
61
|
genCountFromLocalId,
|
|
@@ -63,12 +66,6 @@ import {
|
|
|
63
66
|
subtractNumericUuids,
|
|
64
67
|
} from "./utilities.js";
|
|
65
68
|
|
|
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
69
|
function rangeFinalizationError(expectedStart: number, actualStart: number): LoggingError {
|
|
73
70
|
return new LoggingError("Ranges finalized out of order", {
|
|
74
71
|
expectedStart,
|
|
@@ -76,6 +73,8 @@ function rangeFinalizationError(expectedStart: number, actualStart: number): Log
|
|
|
76
73
|
});
|
|
77
74
|
}
|
|
78
75
|
|
|
76
|
+
const MAX_STRIDE_LENGTH = 1000000;
|
|
77
|
+
|
|
79
78
|
/**
|
|
80
79
|
* See {@link IIdCompressor} and {@link IIdCompressorCore}
|
|
81
80
|
*/
|
|
@@ -93,13 +92,51 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
93
92
|
// The number of IDs generated by the local session
|
|
94
93
|
private localGenCount = 0;
|
|
95
94
|
|
|
95
|
+
/**
|
|
96
|
+
* The sharding state of this compressor, if it has been sharded or is a shard itself.
|
|
97
|
+
* When defined, this compressor operates in "sharding mode" where:
|
|
98
|
+
* - IDs are generated using stride-based allocation (no eager finals)
|
|
99
|
+
* - The normalizer backfills all IDs in each stride cycle
|
|
100
|
+
* - Normal operation resumes when all children are disposed
|
|
101
|
+
*/
|
|
102
|
+
private shardingState?:
|
|
103
|
+
| {
|
|
104
|
+
/**
|
|
105
|
+
* Current stride - used when this compressor has active children.
|
|
106
|
+
* Changes each time shard() is called.
|
|
107
|
+
*/
|
|
108
|
+
currentStride: number;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Original stride - the stride this compressor was created with.
|
|
112
|
+
* Used when this compressor has no children (is a leaf).
|
|
113
|
+
* IMMUTABLE after creation.
|
|
114
|
+
*/
|
|
115
|
+
originalStride: number;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Set of unique child shard IDs (UUIDs) currently active.
|
|
119
|
+
* Each child is assigned a UUID when created via shard().
|
|
120
|
+
* Safe across serialization/deserialization cycles.
|
|
121
|
+
*/
|
|
122
|
+
activeChildIds: Set<SessionId>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* This compressor's unique shard ID within its parent (if it's a child).
|
|
126
|
+
* Undefined for root compressors.
|
|
127
|
+
* Assigned by parent when creating this shard.
|
|
128
|
+
*/
|
|
129
|
+
shardId?: SessionId;
|
|
130
|
+
}
|
|
131
|
+
| undefined;
|
|
132
|
+
|
|
96
133
|
// #endregion
|
|
97
134
|
|
|
98
135
|
// #region Final state
|
|
99
136
|
|
|
100
137
|
/**
|
|
101
138
|
* The gen count to be annotated on the range returned by the next call to `takeNextCreationRange`.
|
|
102
|
-
* This is advanced to `
|
|
139
|
+
* This is advanced to `localGenCount` + 1 each time it is called.
|
|
103
140
|
* On the other hand, when `resetUnfinalizedCreationRange` is called,
|
|
104
141
|
* this is moved back to the start of the unfinalized range, to ensure those IDs are included in the next range.
|
|
105
142
|
*/
|
|
@@ -131,6 +168,12 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
131
168
|
public constructor(
|
|
132
169
|
localSessionIdOrDeserialized: SessionId | Sessions,
|
|
133
170
|
private readonly logger: TelemetryLoggerExt | undefined,
|
|
171
|
+
/**
|
|
172
|
+
* The version this compressor will write out.
|
|
173
|
+
* This applies to both `serialize` and creation range functions.
|
|
174
|
+
* This number also determines what features are available (e.g. sharding support starts at V3).
|
|
175
|
+
*/
|
|
176
|
+
private readonly writeVersion: SerializationVersion,
|
|
134
177
|
) {
|
|
135
178
|
if (typeof localSessionIdOrDeserialized === "string") {
|
|
136
179
|
this.localSessionId = localSessionIdOrDeserialized;
|
|
@@ -164,30 +207,42 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
164
207
|
return lastFinalizedFinal(
|
|
165
208
|
this.ongoingGhostSession.cluster,
|
|
166
209
|
) 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
|
-
}
|
|
210
|
+
}
|
|
174
211
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
212
|
+
// In sharding mode, always generate local IDs with stride-based allocation.
|
|
213
|
+
// Eager finals are disabled because shards backfill IDs not in their stride cycle.
|
|
214
|
+
// If another shard generated them as eager finals, ingesting IDs from another shard
|
|
215
|
+
// would fail depending on timing.
|
|
216
|
+
if (this.shardingState !== undefined) {
|
|
217
|
+
this.telemetryLocalIdCount++;
|
|
218
|
+
const { currentStride } = this.shardingState;
|
|
219
|
+
this.normalizer.addLocalRange(this.localGenCount + 1, currentStride);
|
|
220
|
+
this.localGenCount += currentStride;
|
|
221
|
+
return localIdFromGenCount(this.localGenCount);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Normal ID generation
|
|
225
|
+
this.localGenCount++;
|
|
226
|
+
const lastCluster = this.localSession.getLastCluster();
|
|
227
|
+
if (lastCluster === undefined) {
|
|
188
228
|
this.telemetryLocalIdCount++;
|
|
189
229
|
return this.generateNextLocalId();
|
|
190
230
|
}
|
|
231
|
+
|
|
232
|
+
// If there exists a cluster of final IDs already claimed by the local session that still has room in it,
|
|
233
|
+
// it is known prior to range sequencing what a local ID's corresponding final ID will be.
|
|
234
|
+
// In this case, it is safe to return the final ID immediately. This is guaranteed to be safe because
|
|
235
|
+
// any op that the local session sends that contains one of those final IDs are guaranteed to arrive to
|
|
236
|
+
// collaborators *after* the one containing the creation range.
|
|
237
|
+
const clusterOffset = this.localGenCount - genCountFromLocalId(lastCluster.baseLocalId);
|
|
238
|
+
if (lastCluster.capacity > clusterOffset) {
|
|
239
|
+
this.telemetryEagerFinalIdCount++;
|
|
240
|
+
// Space in the cluster: eager final
|
|
241
|
+
return ((lastCluster.baseFinalId as number) + clusterOffset) as SessionSpaceCompressedId;
|
|
242
|
+
}
|
|
243
|
+
// No space in the cluster, return next local
|
|
244
|
+
this.telemetryLocalIdCount++;
|
|
245
|
+
return this.generateNextLocalId();
|
|
191
246
|
}
|
|
192
247
|
|
|
193
248
|
public generateDocumentUniqueId():
|
|
@@ -210,6 +265,9 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
210
265
|
* {@inheritdoc IIdCompressorCore.beginGhostSession}
|
|
211
266
|
*/
|
|
212
267
|
public beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void): void {
|
|
268
|
+
if (this.shardingState) {
|
|
269
|
+
throw new Error("Cannot start a ghost session while sharded.");
|
|
270
|
+
}
|
|
213
271
|
this.startGhostSession(ghostSessionId);
|
|
214
272
|
try {
|
|
215
273
|
ghostSessionCallback();
|
|
@@ -218,6 +276,202 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
218
276
|
}
|
|
219
277
|
}
|
|
220
278
|
|
|
279
|
+
/**
|
|
280
|
+
* {@inheritdoc IIdCompressorCore.shard}
|
|
281
|
+
*/
|
|
282
|
+
public shard(newShardCount: number): SerializedIdCompressorWithOngoingSession[] {
|
|
283
|
+
if (!Number.isSafeInteger(newShardCount) || newShardCount <= 0) {
|
|
284
|
+
throw new Error("Shard count must be a positive safe integer");
|
|
285
|
+
}
|
|
286
|
+
if (this.ongoingGhostSession) {
|
|
287
|
+
throw new Error("Cannot shard during ghost session");
|
|
288
|
+
}
|
|
289
|
+
if (this.writeVersion < SerializationVersion.V3) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Sharding requires document version ${SerializationVersion.V3} or higher, but this document is version ${this.writeVersion}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const currentStride = this.shardingState?.currentStride ?? 1;
|
|
296
|
+
const newStride = currentStride * (newShardCount + 1);
|
|
297
|
+
|
|
298
|
+
// ID usage is exponential in stride count. Limit to practical size to avoid overflow.
|
|
299
|
+
if (newStride > MAX_STRIDE_LENGTH) {
|
|
300
|
+
throw new Error("Sharding limit reached.");
|
|
301
|
+
}
|
|
302
|
+
assert(Number.isSafeInteger(newStride), 0xd52 /* Shard stride must be a safe integer */);
|
|
303
|
+
|
|
304
|
+
if (this.shardingState === undefined) {
|
|
305
|
+
// First time sharding - initialize state
|
|
306
|
+
this.shardingState = {
|
|
307
|
+
currentStride: newStride,
|
|
308
|
+
originalStride: 1,
|
|
309
|
+
activeChildIds: new Set(),
|
|
310
|
+
};
|
|
311
|
+
} else {
|
|
312
|
+
this.shardingState.currentStride = newStride;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Cache to avoid repeating per child
|
|
316
|
+
const serialized = this.serialize(true);
|
|
317
|
+
const { localGenCount: parentGenCount } = this;
|
|
318
|
+
|
|
319
|
+
const shards: SerializedIdCompressorWithOngoingSession[] = [];
|
|
320
|
+
|
|
321
|
+
// Children are positioned at consecutive positions within the parents current stride cycle
|
|
322
|
+
// e.g. if parent owns the "evens" space (-2, -4, -6, ...) and is sharded into 3 (two children)
|
|
323
|
+
// while at -8, then the child shards will start at -10 and -12 with a stride of 6
|
|
324
|
+
for (let i = 1; i <= newShardCount; i++) {
|
|
325
|
+
const childShardId = createSessionId();
|
|
326
|
+
this.shardingState.activeChildIds.add(childShardId);
|
|
327
|
+
|
|
328
|
+
const childLocalGenCount = parentGenCount + i * currentStride;
|
|
329
|
+
|
|
330
|
+
// This code results in a double roundtrip, but is probably plenty fast enough for scenarios that need sharding,
|
|
331
|
+
// as they necessarily have process boundaries to deal with.
|
|
332
|
+
// If this ever became an issue we can implement a fast clone of compressors
|
|
333
|
+
const child = deserializeIdCompressor(serialized, this.writeVersion) as IdCompressor;
|
|
334
|
+
const genCountJump = childLocalGenCount - parentGenCount;
|
|
335
|
+
assert(
|
|
336
|
+
child.localGenCount === parentGenCount && genCountJump > 0,
|
|
337
|
+
0xd53 /* Child offsets incorrectly calculated. */,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
child.normalizer.addLocalRange(child.localGenCount + 1, genCountJump);
|
|
341
|
+
child.localGenCount = childLocalGenCount;
|
|
342
|
+
child.shardingState = {
|
|
343
|
+
currentStride: newStride,
|
|
344
|
+
originalStride: newStride,
|
|
345
|
+
activeChildIds: new Set(),
|
|
346
|
+
shardId: childShardId,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
shards.push(child.serialize(true));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return shards;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* {@inheritdoc IIdCompressorCore.synchronizeWithShard}
|
|
356
|
+
*/
|
|
357
|
+
public synchronizeWithShard(syncToken: ShardSynchronizationToken): void {
|
|
358
|
+
const isNowLeaf = this.synchronizeChild(syncToken);
|
|
359
|
+
// A disposal token additionally reclaims the child's ID space. If reclaiming the last child
|
|
360
|
+
// returns this compressor to a leaf and it is the root (originalStride === 1), exit sharding mode.
|
|
361
|
+
if (syncToken.disposed) {
|
|
362
|
+
assert(this.shardingState !== undefined, 0xd54 /* Must be sharded */);
|
|
363
|
+
if (isNowLeaf && this.shardingState.originalStride === 1) {
|
|
364
|
+
this.shardingState = undefined;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private synchronizeChild(syncToken: ShardToken): boolean {
|
|
370
|
+
const childDisposed = syncToken.disposed;
|
|
371
|
+
if (this.writeVersion < SerializationVersion.V3) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`Sharding requires document version ${SerializationVersion.V3} or higher, but this document is version ${this.writeVersion}`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
if (this.shardingState === undefined) {
|
|
377
|
+
throw new Error("Must be in sharding mode.");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const childShardId = syncToken.shardId;
|
|
381
|
+
const { activeChildIds, originalStride } = this.shardingState;
|
|
382
|
+
|
|
383
|
+
// Verify this child belongs to us
|
|
384
|
+
if (!activeChildIds.has(childShardId)) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`Cannot synchronize with child with ID ${childShardId}: not in active children set`,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Only disposal removes the child from the active set. A plain synchronization leaves the
|
|
391
|
+
// child active so it can continue generating IDs and be synchronized with again later.
|
|
392
|
+
if (childDisposed) {
|
|
393
|
+
activeChildIds.delete(childShardId);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const isLeaf = activeChildIds.size === 0;
|
|
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;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return isLeaf;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* {@inheritdoc IIdCompressorCore.dispose}
|
|
421
|
+
*/
|
|
422
|
+
public disposeShard(): ShardSynchronizationToken | undefined {
|
|
423
|
+
if (this.shardingState === undefined) {
|
|
424
|
+
return undefined;
|
|
425
|
+
} else {
|
|
426
|
+
if (this.shardingState.activeChildIds.size > 0) {
|
|
427
|
+
throw new Error("Cannot dispose a shard with active children.");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// A disposal token is a sync token that additionally signals reclamation of this shard's ID space.
|
|
432
|
+
const token = this.makeShardToken(true);
|
|
433
|
+
|
|
434
|
+
// Make this compressor unusable by replacing all methods with throwing stubs
|
|
435
|
+
// This is a one-time mutation that avoids runtime checks in every method
|
|
436
|
+
makeCompressorUnusable(this);
|
|
437
|
+
|
|
438
|
+
return token;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* {@inheritdoc IIdCompressorCore.getShardSyncToken}
|
|
443
|
+
*/
|
|
444
|
+
public getShardSyncToken(): ShardSynchronizationToken | undefined {
|
|
445
|
+
if (this.shardingState === undefined) {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return this.makeShardToken(false);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Builds a token describing this shard's current progress.
|
|
454
|
+
* @param disposed - Whether the token also signals disposal (reclamation of this shard's ID space).
|
|
455
|
+
*/
|
|
456
|
+
private makeShardToken(disposed: boolean): ShardSynchronizationToken {
|
|
457
|
+
assert(this.shardingState !== undefined, 0xd55 /* Compressor is not sharded. */);
|
|
458
|
+
|
|
459
|
+
// Root cannot produce a token (shardId is undefined for root)
|
|
460
|
+
if (this.shardingState.shardId === undefined) {
|
|
461
|
+
throw new Error(
|
|
462
|
+
"Cannot get a token for a root compressor - only shards can be disposed",
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const token: ShardToken = {
|
|
467
|
+
localGenCount: this.localGenCount,
|
|
468
|
+
shardId: this.shardingState.shardId,
|
|
469
|
+
disposed,
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
return token as unknown as ShardSynchronizationToken;
|
|
473
|
+
}
|
|
474
|
+
|
|
221
475
|
private generateNextLocalId(): LocalCompressedId {
|
|
222
476
|
// Must tell the normalizer that we generated a local ID
|
|
223
477
|
this.normalizer.addLocalRange(this.localGenCount, 1);
|
|
@@ -225,6 +479,14 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
225
479
|
}
|
|
226
480
|
|
|
227
481
|
public takeNextCreationRange(): IdCreationRange {
|
|
482
|
+
// Child (non-root) shards must not submit their own creation ranges. All shards in a shard tree share a
|
|
483
|
+
// single session, and only the root shard is responsible for finalizing that session's IDs with the server.
|
|
484
|
+
// A child's generated IDs are reconciled with the root via shard synchronization tokens (see
|
|
485
|
+
// synchronizeWithShard / disposeShard), not by independently sending ranges, which would overlap and
|
|
486
|
+
// conflict with the root's ranges for the shared session.
|
|
487
|
+
if (this.shardingState?.shardId !== undefined) {
|
|
488
|
+
throw new Error("Cannot take a creation range from a non-root shard.");
|
|
489
|
+
}
|
|
228
490
|
assert(
|
|
229
491
|
!this.ongoingGhostSession,
|
|
230
492
|
0x8a6 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
@@ -577,11 +839,24 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
577
839
|
sessionIndex++;
|
|
578
840
|
}
|
|
579
841
|
}
|
|
842
|
+
const sizeOfUuid = 2;
|
|
843
|
+
const shardingStateSize =
|
|
844
|
+
hasLocalState && this.writeVersion >= SerializationVersion.V3
|
|
845
|
+
? this.shardingState === undefined
|
|
846
|
+
? 1 // Boolean: no sharding state
|
|
847
|
+
: 1 + // Boolean: has sharding state
|
|
848
|
+
2 + // Numbers: currentStride, originalStride (Float64)
|
|
849
|
+
1 + // Number: activeChildIds count (Float64)
|
|
850
|
+
this.shardingState.activeChildIds.size * sizeOfUuid + // UUIDs: each is 128-bit
|
|
851
|
+
1 + // Boolean: hasShardId
|
|
852
|
+
(this.shardingState.shardId === undefined ? 0 : sizeOfUuid) // UUID: 128-bit if present
|
|
853
|
+
: 0;
|
|
580
854
|
const localStateSize = hasLocalState
|
|
581
855
|
? 1 + // generated ID count
|
|
582
856
|
1 + // next range base genCount
|
|
583
857
|
1 + // count of normalizer pairs
|
|
584
|
-
normalizer.idRanges.size * 2 // pairs
|
|
858
|
+
normalizer.idRanges.size * 2 + // pairs
|
|
859
|
+
shardingStateSize // sharding state if present
|
|
585
860
|
: 0;
|
|
586
861
|
// Layout size, in 8 byte increments
|
|
587
862
|
const totalSize =
|
|
@@ -589,14 +864,14 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
589
864
|
1 + // hasLocalState
|
|
590
865
|
1 + // session count
|
|
591
866
|
1 + // cluster count
|
|
592
|
-
sessionIndexMap.size *
|
|
867
|
+
sessionIndexMap.size * sizeOfUuid + // session IDs
|
|
593
868
|
finalSpace.clusters.length * 3 + // clusters: (sessionIndex, capacity, count)[]
|
|
594
869
|
localStateSize; // local state, if present
|
|
595
870
|
|
|
596
871
|
const serializedFloat = new Float64Array(totalSize);
|
|
597
872
|
const serializedUint = new BigUint64Array(serializedFloat.buffer);
|
|
598
873
|
let index = 0;
|
|
599
|
-
index = writeNumber(serializedFloat, index,
|
|
874
|
+
index = writeNumber(serializedFloat, index, this.writeVersion);
|
|
600
875
|
index = writeBoolean(serializedFloat, index, hasLocalState);
|
|
601
876
|
index = writeNumber(serializedFloat, index, sessionIndexMap.size);
|
|
602
877
|
index = writeNumber(serializedFloat, index, finalSpace.clusters.length);
|
|
@@ -623,6 +898,35 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
623
898
|
index = writeNumber(serializedFloat, index, leadingGenCount);
|
|
624
899
|
index = writeNumber(serializedFloat, index, count);
|
|
625
900
|
}
|
|
901
|
+
|
|
902
|
+
// Write sharding state (version 3+)
|
|
903
|
+
if (this.writeVersion >= SerializationVersion.V3) {
|
|
904
|
+
index = writeBoolean(serializedFloat, index, this.shardingState !== undefined);
|
|
905
|
+
if (this.shardingState !== undefined) {
|
|
906
|
+
index = writeNumber(serializedFloat, index, this.shardingState.currentStride);
|
|
907
|
+
index = writeNumber(serializedFloat, index, this.shardingState.originalStride);
|
|
908
|
+
|
|
909
|
+
// Write activeChildIds Set as count + array of NumericUuids
|
|
910
|
+
index = writeNumber(serializedFloat, index, this.shardingState.activeChildIds.size);
|
|
911
|
+
for (const childId of this.shardingState.activeChildIds) {
|
|
912
|
+
index = writeNumericUuid(serializedUint, index, numericUuidFromStableId(childId));
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Write shardId (optional)
|
|
916
|
+
index = writeBoolean(
|
|
917
|
+
serializedFloat,
|
|
918
|
+
index,
|
|
919
|
+
this.shardingState.shardId !== undefined,
|
|
920
|
+
);
|
|
921
|
+
if (this.shardingState.shardId !== undefined) {
|
|
922
|
+
index = writeNumericUuid(
|
|
923
|
+
serializedUint,
|
|
924
|
+
index,
|
|
925
|
+
numericUuidFromStableId(this.shardingState.shardId),
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
626
930
|
}
|
|
627
931
|
|
|
628
932
|
assert(index === totalSize, 0x75b /* Serialized size was incorrectly calculated. */);
|
|
@@ -640,41 +944,45 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
640
944
|
params:
|
|
641
945
|
| {
|
|
642
946
|
serialized: SerializedIdCompressorWithOngoingSession;
|
|
947
|
+
requestedWriteVersion: SerializationVersion;
|
|
643
948
|
logger?: TelemetryLoggerExt | undefined;
|
|
644
949
|
newSessionId?: never;
|
|
645
950
|
}
|
|
646
951
|
| {
|
|
647
952
|
serialized: SerializedIdCompressorWithNoSession;
|
|
648
953
|
newSessionId: SessionId;
|
|
954
|
+
requestedWriteVersion: SerializationVersion;
|
|
649
955
|
logger?: TelemetryLoggerExt | undefined;
|
|
650
956
|
},
|
|
651
957
|
): IdCompressor {
|
|
652
|
-
const { serialized, newSessionId, logger } = params;
|
|
958
|
+
const { serialized, newSessionId: sessionId, logger, requestedWriteVersion } = params;
|
|
653
959
|
const buffer = stringToBuffer(serialized, "base64");
|
|
654
960
|
const index: Index = {
|
|
655
961
|
index: 0,
|
|
656
962
|
bufferFloat: new Float64Array(buffer),
|
|
657
963
|
bufferUint: new BigUint64Array(buffer),
|
|
658
964
|
};
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
965
|
+
const serializedVersion = readNumber(index) as SerializationVersion;
|
|
966
|
+
// If requested version is < the serialized version, we must write the version serialized
|
|
967
|
+
// to avoid losing data.
|
|
968
|
+
const writeVersion = Math.max(
|
|
969
|
+
requestedWriteVersion,
|
|
970
|
+
serializedVersion,
|
|
971
|
+
) as SerializationVersion;
|
|
972
|
+
|
|
973
|
+
switch (serializedVersion) {
|
|
974
|
+
case 2:
|
|
975
|
+
case 3: {
|
|
976
|
+
break;
|
|
666
977
|
}
|
|
667
978
|
default: {
|
|
979
|
+
if (serializedVersion === 1) {
|
|
980
|
+
throw new Error("IdCompressor version 1.0 is no longer supported.");
|
|
981
|
+
}
|
|
668
982
|
throw new Error("Unknown IdCompressor serialized version.");
|
|
669
983
|
}
|
|
670
984
|
}
|
|
671
|
-
}
|
|
672
985
|
|
|
673
|
-
static deserialize2_0(
|
|
674
|
-
index: Index,
|
|
675
|
-
sessionId: SessionId | undefined,
|
|
676
|
-
logger: TelemetryLoggerExt | undefined,
|
|
677
|
-
): IdCompressor {
|
|
678
986
|
const hasLocalState = readBoolean(index);
|
|
679
987
|
const sessionCount = readNumber(index);
|
|
680
988
|
const clusterCount = readNumber(index);
|
|
@@ -700,7 +1008,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
700
1008
|
sessions.push([numeric, new Session(numeric)]);
|
|
701
1009
|
}
|
|
702
1010
|
|
|
703
|
-
const compressor = new IdCompressor(new Sessions(sessions), logger);
|
|
1011
|
+
const compressor = new IdCompressor(new Sessions(sessions), logger, writeVersion);
|
|
704
1012
|
|
|
705
1013
|
// Clusters
|
|
706
1014
|
let baseFinalId = 0;
|
|
@@ -727,6 +1035,38 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
727
1035
|
for (let i = 0; i < normalizerCount; i++) {
|
|
728
1036
|
compressor.normalizer.addLocalRange(readNumber(index), readNumber(index));
|
|
729
1037
|
}
|
|
1038
|
+
|
|
1039
|
+
// Sharding state (version 3+)
|
|
1040
|
+
if (serializedVersion >= 3) {
|
|
1041
|
+
const hasShardingState = readBoolean(index);
|
|
1042
|
+
if (hasShardingState) {
|
|
1043
|
+
const currentStride = readNumber(index);
|
|
1044
|
+
const originalStride = readNumber(index);
|
|
1045
|
+
|
|
1046
|
+
// Read activeChildIds Set
|
|
1047
|
+
const activeChildCount = readNumber(index);
|
|
1048
|
+
const activeChildIds = new Set<SessionId>();
|
|
1049
|
+
for (let i = 0; i < activeChildCount; i++) {
|
|
1050
|
+
const childIdNumeric = readNumericUuid(index);
|
|
1051
|
+
// Safe cast: these were serialized as SessionIds
|
|
1052
|
+
activeChildIds.add(stableIdFromNumericUuid(childIdNumeric) as SessionId);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Read shardId (optional)
|
|
1056
|
+
const hasShardId = readBoolean(index);
|
|
1057
|
+
const shardId: SessionId | undefined = hasShardId
|
|
1058
|
+
? (stableIdFromNumericUuid(readNumericUuid(index)) as SessionId)
|
|
1059
|
+
: undefined;
|
|
1060
|
+
|
|
1061
|
+
// Build sharding state object, conditionally including shardId
|
|
1062
|
+
compressor.shardingState = {
|
|
1063
|
+
currentStride,
|
|
1064
|
+
originalStride,
|
|
1065
|
+
activeChildIds,
|
|
1066
|
+
...(shardId !== undefined && { shardId }),
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
730
1070
|
}
|
|
731
1071
|
|
|
732
1072
|
assert(
|
|
@@ -747,6 +1087,34 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
747
1087
|
) {
|
|
748
1088
|
return false;
|
|
749
1089
|
}
|
|
1090
|
+
// Shard ownership affects future local ID allocation, even when the generated IDs match.
|
|
1091
|
+
// It is not part of finalized state, so ignore it when comparing only that state.
|
|
1092
|
+
if (includeLocalState) {
|
|
1093
|
+
const state = this.shardingState;
|
|
1094
|
+
const otherState = other.shardingState;
|
|
1095
|
+
if (state === undefined || otherState === undefined) {
|
|
1096
|
+
// Two unsharded compressors match here; a sharded and an unsharded compressor do not.
|
|
1097
|
+
if (state !== otherState) {
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
if (
|
|
1102
|
+
state.currentStride !== otherState.currentStride ||
|
|
1103
|
+
state.originalStride !== otherState.originalStride ||
|
|
1104
|
+
state.shardId !== otherState.shardId ||
|
|
1105
|
+
state.activeChildIds.size !== otherState.activeChildIds.size
|
|
1106
|
+
) {
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
// Child ownership is an unordered set. Equal sizes and membership imply equal sets,
|
|
1110
|
+
// regardless of insertion order or whether the state was deserialized.
|
|
1111
|
+
for (const childId of state.activeChildIds) {
|
|
1112
|
+
if (!otherState.activeChildIds.has(childId)) {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
750
1118
|
return (
|
|
751
1119
|
this.sessions.equals(other.sessions, includeLocalState) &&
|
|
752
1120
|
this.finalSpace.equals(other.finalSpace)
|
|
@@ -756,73 +1124,94 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
756
1124
|
|
|
757
1125
|
/**
|
|
758
1126
|
* Create a new {@link IIdCompressor}.
|
|
759
|
-
*
|
|
760
|
-
* @
|
|
1127
|
+
* @param writeVersion - The version the compressor will write when serializing. Use SerializationVersion.V2 for base format, or SerializationVersion.V3 for sharding support.
|
|
1128
|
+
* @param logger - Optional telemetry logger.
|
|
1129
|
+
* @internal
|
|
761
1130
|
*/
|
|
762
|
-
export function createIdCompressor(
|
|
1131
|
+
export function createIdCompressor(
|
|
1132
|
+
writeVersion: SerializationVersion,
|
|
1133
|
+
logger?: ITelemetryBaseLogger,
|
|
1134
|
+
): IIdCompressor & IIdCompressorCore;
|
|
763
1135
|
/**
|
|
764
1136
|
* Create a new {@link IIdCompressor}.
|
|
765
1137
|
* @param sessionId - The seed ID for the compressor.
|
|
766
|
-
*
|
|
767
|
-
* @
|
|
1138
|
+
* @param writeVersion - The version the compressor will write when serializing. Use SerializationVersion.V2 for base format, or SerializationVersion.V3 for sharding support.
|
|
1139
|
+
* @param logger - Optional telemetry logger.
|
|
1140
|
+
* @internal
|
|
768
1141
|
*/
|
|
769
1142
|
export function createIdCompressor(
|
|
770
1143
|
sessionId: SessionId,
|
|
1144
|
+
writeVersion: SerializationVersion,
|
|
771
1145
|
logger?: ITelemetryBaseLogger,
|
|
772
|
-
): IIdCompressor;
|
|
1146
|
+
): IIdCompressor & IIdCompressorCore;
|
|
773
1147
|
export function createIdCompressor(
|
|
774
|
-
|
|
1148
|
+
sessionIdOrDocumentVersion: SessionId | SerializationVersion,
|
|
1149
|
+
writeVersionOrLogger?: SerializationVersion | ITelemetryBaseLogger,
|
|
775
1150
|
loggerOrUndefined?: ITelemetryBaseLogger,
|
|
776
1151
|
): IIdCompressor & IIdCompressorCore {
|
|
777
1152
|
let localSessionId: SessionId;
|
|
1153
|
+
let writeVersion: SerializationVersion;
|
|
778
1154
|
let logger: ITelemetryBaseLogger | undefined;
|
|
779
|
-
|
|
780
|
-
|
|
1155
|
+
|
|
1156
|
+
if (typeof sessionIdOrDocumentVersion === "string") {
|
|
1157
|
+
// Called with sessionId, writeVersion, logger?
|
|
1158
|
+
localSessionId = sessionIdOrDocumentVersion;
|
|
1159
|
+
writeVersion = writeVersionOrLogger as SerializationVersion;
|
|
1160
|
+
logger = loggerOrUndefined;
|
|
781
1161
|
} else {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
localSessionId = createSessionId();
|
|
787
|
-
logger = sessionIdOrLogger;
|
|
788
|
-
}
|
|
1162
|
+
// Called with writeVersion, logger?
|
|
1163
|
+
localSessionId = createSessionId();
|
|
1164
|
+
writeVersion = sessionIdOrDocumentVersion;
|
|
1165
|
+
logger = writeVersionOrLogger as ITelemetryBaseLogger | undefined;
|
|
789
1166
|
}
|
|
1167
|
+
|
|
790
1168
|
const compressor = new IdCompressor(
|
|
791
1169
|
localSessionId,
|
|
792
1170
|
logger === undefined ? undefined : createChildLogger({ logger }),
|
|
1171
|
+
writeVersion,
|
|
793
1172
|
);
|
|
794
1173
|
return compressor;
|
|
795
1174
|
}
|
|
796
1175
|
|
|
797
1176
|
/**
|
|
798
1177
|
* Deserializes the supplied state into an ID compressor.
|
|
799
|
-
*
|
|
800
|
-
* @
|
|
1178
|
+
* @param serialized - The serialized compressor state with an ongoing session.
|
|
1179
|
+
* @param writeVersion - The version the compressor will write when serializing. This determines what features are available (e.g., V3 enables sharding support).
|
|
1180
|
+
* @param logger - Optional telemetry logger.
|
|
1181
|
+
* @internal
|
|
801
1182
|
*/
|
|
802
1183
|
export function deserializeIdCompressor(
|
|
803
1184
|
serialized: SerializedIdCompressorWithOngoingSession,
|
|
1185
|
+
writeVersion: SerializationVersion,
|
|
804
1186
|
logger?: ITelemetryLoggerExt,
|
|
805
|
-
): IIdCompressor;
|
|
1187
|
+
): IIdCompressor & IIdCompressorCore;
|
|
806
1188
|
/**
|
|
807
1189
|
* Deserializes the supplied state into an ID compressor.
|
|
808
|
-
*
|
|
809
|
-
* @
|
|
1190
|
+
* @param serialized - The serialized compressor state without a session.
|
|
1191
|
+
* @param newSessionId - The session ID to use for the deserialized compressor.
|
|
1192
|
+
* @param writeVersion - The version the compressor will write when serializing. This determines what features are available (e.g., V3 enables sharding support).
|
|
1193
|
+
* @param logger - Optional telemetry logger.
|
|
1194
|
+
* @internal
|
|
810
1195
|
*/
|
|
811
1196
|
export function deserializeIdCompressor(
|
|
812
1197
|
serialized: SerializedIdCompressorWithNoSession,
|
|
813
1198
|
newSessionId: SessionId,
|
|
1199
|
+
writeVersion: SerializationVersion,
|
|
814
1200
|
logger?: ITelemetryLoggerExt,
|
|
815
|
-
): IIdCompressor;
|
|
1201
|
+
): IIdCompressor & IIdCompressorCore;
|
|
816
1202
|
export function deserializeIdCompressor(
|
|
817
1203
|
serialized: SerializedIdCompressor | SerializedIdCompressorWithNoSession,
|
|
818
|
-
|
|
1204
|
+
sessionIdOrDocumentVersion: SessionId | SerializationVersion,
|
|
1205
|
+
writeVersionOrLogger?: SerializationVersion | ITelemetryLoggerExt,
|
|
819
1206
|
loggerOrUndefined?: ITelemetryLoggerExt,
|
|
820
1207
|
): IIdCompressor & IIdCompressorCore {
|
|
821
|
-
if (typeof
|
|
1208
|
+
if (typeof sessionIdOrDocumentVersion === "string") {
|
|
1209
|
+
// Called with (serialized, sessionId, writeVersion, logger?)
|
|
822
1210
|
return IdCompressor.deserialize({
|
|
823
1211
|
serialized: serialized as SerializedIdCompressorWithNoSession,
|
|
824
1212
|
logger: extractTelemetryLoggerExt<{ PossiblyUndefined: true }>(loggerOrUndefined),
|
|
825
|
-
newSessionId:
|
|
1213
|
+
newSessionId: sessionIdOrDocumentVersion,
|
|
1214
|
+
requestedWriteVersion: writeVersionOrLogger as SerializationVersion,
|
|
826
1215
|
});
|
|
827
1216
|
}
|
|
828
1217
|
|
|
@@ -830,18 +1219,53 @@ export function deserializeIdCompressor(
|
|
|
830
1219
|
loggerOrUndefined === undefined,
|
|
831
1220
|
0xc2d /* logger would be in sessionIdOrLogger in this codepath */,
|
|
832
1221
|
);
|
|
1222
|
+
// Called with (serialized, writeVersion, logger?)
|
|
833
1223
|
return IdCompressor.deserialize({
|
|
834
1224
|
serialized: serialized as SerializedIdCompressorWithOngoingSession,
|
|
835
|
-
logger: extractTelemetryLoggerExt<{ PossiblyUndefined: true }>(
|
|
1225
|
+
logger: extractTelemetryLoggerExt<{ PossiblyUndefined: true }>(
|
|
1226
|
+
writeVersionOrLogger as ITelemetryLoggerExt | undefined,
|
|
1227
|
+
),
|
|
1228
|
+
requestedWriteVersion: sessionIdOrDocumentVersion,
|
|
836
1229
|
});
|
|
837
1230
|
}
|
|
838
1231
|
|
|
1232
|
+
/**
|
|
1233
|
+
* Throws an error indicating the compressor has been disposed.
|
|
1234
|
+
*/
|
|
1235
|
+
const throwDisposed = (): never => {
|
|
1236
|
+
throw new Error(
|
|
1237
|
+
"This compressor has been disposed via disposeShard() and can no longer be used.",
|
|
1238
|
+
);
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Makes an IdCompressor instance unusable by replacing all its methods with functions that throw.
|
|
1243
|
+
* This is called once when disposeShard() is invoked to make the compressor permanently unusable.
|
|
1244
|
+
* @param compressor - The compressor instance to make unusable
|
|
1245
|
+
*/
|
|
1246
|
+
function makeCompressorUnusable(compressor: IdCompressor): void {
|
|
1247
|
+
// Get all method names from the prototype (where class methods live)
|
|
1248
|
+
const proto = Object.getPrototypeOf(compressor) as object;
|
|
1249
|
+
for (const key of Reflect.ownKeys(proto)) {
|
|
1250
|
+
if (key !== "constructor" && typeof Reflect.get(proto, key) === "function") {
|
|
1251
|
+
// Shadow the method only on this instance so other compressors remain usable.
|
|
1252
|
+
// Omitting `enumerable` keeps new overrides non-enumerable, like class methods.
|
|
1253
|
+
// Reflection supports string and symbol keys without adding an index signature.
|
|
1254
|
+
Object.defineProperty(compressor, key, {
|
|
1255
|
+
value: throwDisposed,
|
|
1256
|
+
configurable: true,
|
|
1257
|
+
writable: true,
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
839
1263
|
/**
|
|
840
1264
|
* Serializes an ID compressor.
|
|
841
1265
|
* @param compressor - The compressor to serialize.
|
|
842
1266
|
* @param withSession - If true, the serialized state will include local session
|
|
843
1267
|
* state (for stashing). If false, only finalized state is included (for summaries).
|
|
844
|
-
* @
|
|
1268
|
+
* @internal
|
|
845
1269
|
*/
|
|
846
1270
|
export function serializeIdCompressor(
|
|
847
1271
|
compressor: IIdCompressor,
|
|
@@ -852,7 +1276,7 @@ export function serializeIdCompressor(
|
|
|
852
1276
|
* @param compressor - The compressor to serialize.
|
|
853
1277
|
* @param withSession - If true, the serialized state will include local session
|
|
854
1278
|
* state (for stashing). If false, only finalized state is included (for summaries).
|
|
855
|
-
* @
|
|
1279
|
+
* @internal
|
|
856
1280
|
*/
|
|
857
1281
|
export function serializeIdCompressor(
|
|
858
1282
|
compressor: IIdCompressor,
|