@fluid-experimental/tree 0.59.4001 → 1.1.0-75972
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/dist/EditLog.d.ts.map +1 -1
- package/dist/EditLog.js +4 -7
- package/dist/EditLog.js.map +1 -1
- package/dist/LogViewer.d.ts.map +1 -1
- package/dist/LogViewer.js +1 -7
- package/dist/LogViewer.js.map +1 -1
- package/dist/SharedTree.d.ts +89 -30
- package/dist/SharedTree.d.ts.map +1 -1
- package/dist/SharedTree.js +97 -62
- package/dist/SharedTree.js.map +1 -1
- package/dist/SharedTreeEncoder.d.ts +1 -1
- package/dist/SharedTreeEncoder.d.ts.map +1 -1
- package/dist/SharedTreeEncoder.js +6 -12
- package/dist/SharedTreeEncoder.js.map +1 -1
- package/dist/TransactionInternal.d.ts.map +1 -1
- package/dist/TransactionInternal.js +4 -7
- package/dist/TransactionInternal.js.map +1 -1
- package/dist/id-compressor/IdCompressor.d.ts +3 -1
- package/dist/id-compressor/IdCompressor.d.ts.map +1 -1
- package/dist/id-compressor/IdCompressor.js +45 -17
- package/dist/id-compressor/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/lib/EditLog.d.ts.map +1 -1
- package/lib/EditLog.js +4 -7
- package/lib/EditLog.js.map +1 -1
- package/lib/LogViewer.d.ts.map +1 -1
- package/lib/LogViewer.js +1 -7
- package/lib/LogViewer.js.map +1 -1
- package/lib/SharedTree.d.ts +89 -30
- package/lib/SharedTree.d.ts.map +1 -1
- package/lib/SharedTree.js +98 -63
- package/lib/SharedTree.js.map +1 -1
- package/lib/SharedTreeEncoder.d.ts +1 -1
- package/lib/SharedTreeEncoder.d.ts.map +1 -1
- package/lib/SharedTreeEncoder.js +6 -12
- package/lib/SharedTreeEncoder.js.map +1 -1
- package/lib/TransactionInternal.d.ts.map +1 -1
- package/lib/TransactionInternal.js +4 -7
- package/lib/TransactionInternal.js.map +1 -1
- package/lib/id-compressor/IdCompressor.d.ts +3 -1
- package/lib/id-compressor/IdCompressor.d.ts.map +1 -1
- package/lib/id-compressor/IdCompressor.js +45 -17
- package/lib/id-compressor/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/test/MergeHealthTelemetryHeartbeat.tests.js +1 -1
- package/lib/test/MergeHealthTelemetryHeartbeat.tests.js.map +1 -1
- package/lib/test/SessionIdNormalizer.tests.js +4 -6
- package/lib/test/SessionIdNormalizer.tests.js.map +1 -1
- package/lib/test/Summary.tests.js +3 -6
- package/lib/test/Summary.tests.js.map +1 -1
- package/lib/test/fuzz/SharedTreeFuzzTests.d.ts.map +1 -1
- package/lib/test/fuzz/SharedTreeFuzzTests.js +3 -1
- package/lib/test/fuzz/SharedTreeFuzzTests.js.map +1 -1
- package/lib/test/utilities/SharedTreeTests.d.ts.map +1 -1
- package/lib/test/utilities/SharedTreeTests.js +59 -60
- package/lib/test/utilities/SharedTreeTests.js.map +1 -1
- package/lib/test/utilities/SharedTreeVersioningTests.d.ts.map +1 -1
- package/lib/test/utilities/SharedTreeVersioningTests.js +19 -19
- package/lib/test/utilities/SharedTreeVersioningTests.js.map +1 -1
- package/lib/test/utilities/TestNode.d.ts.map +1 -1
- package/lib/test/utilities/TestNode.js +2 -12
- package/lib/test/utilities/TestNode.js.map +1 -1
- package/lib/test/utilities/TestUtilities.d.ts.map +1 -1
- package/lib/test/utilities/TestUtilities.js +11 -11
- package/lib/test/utilities/TestUtilities.js.map +1 -1
- package/package.json +19 -32
- package/src/EditLog.ts +21 -23
- package/src/LogViewer.ts +1 -6
- package/src/SharedTree.ts +200 -50
- package/src/SharedTreeEncoder.ts +7 -18
- package/src/TransactionInternal.ts +11 -13
- package/src/id-compressor/IdCompressor.ts +46 -15
- package/src/index.ts +4 -0
package/src/SharedTree.ts
CHANGED
|
@@ -27,7 +27,15 @@ import { ChildLogger, ITelemetryLoggerPropertyBags, PerformanceEvent } from '@fl
|
|
|
27
27
|
import { ISummaryTreeWithStats } from '@fluidframework/runtime-definitions';
|
|
28
28
|
import { assert, assertNotUndefined, fail, copyPropertyIfDefined, noop } from './Common';
|
|
29
29
|
import { EditHandle, EditLog, getNumberOfHandlesFromEditLogSummary, OrderedEditSet } from './EditLog';
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
EditId,
|
|
32
|
+
NodeId,
|
|
33
|
+
StableNodeId,
|
|
34
|
+
DetachedSequenceId,
|
|
35
|
+
OpSpaceNodeId,
|
|
36
|
+
isDetachedSequenceId,
|
|
37
|
+
AttributionId,
|
|
38
|
+
} from './Identifiers';
|
|
31
39
|
import { initialTree } from './InitialTree';
|
|
32
40
|
import {
|
|
33
41
|
CachingLogViewer,
|
|
@@ -88,6 +96,75 @@ import { TransactionInternal } from './TransactionInternal';
|
|
|
88
96
|
import { IdCompressor, createSessionId } from './id-compressor';
|
|
89
97
|
import { convertEditIds } from './IdConversion';
|
|
90
98
|
import { MutableStringInterner } from './StringInterner';
|
|
99
|
+
import { nilUuid } from './UuidUtilities';
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The write format and associated options used to construct a `SharedTree`
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
export type SharedTreeArgs<WF extends WriteFormat = WriteFormat> = [writeFormat: WF, options?: SharedTreeOptions<WF>];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The type of shared tree options for a given write format
|
|
109
|
+
* @public
|
|
110
|
+
*/
|
|
111
|
+
export type SharedTreeOptions<
|
|
112
|
+
WF extends WriteFormat,
|
|
113
|
+
HistoryCompatibility extends 'Forwards' | 'None' = 'Forwards'
|
|
114
|
+
> = Omit<
|
|
115
|
+
WF extends WriteFormat.v0_0_2
|
|
116
|
+
? SharedTreeOptions_0_0_2
|
|
117
|
+
: WF extends WriteFormat.v0_1_1
|
|
118
|
+
? SharedTreeOptions_0_1_1
|
|
119
|
+
: never,
|
|
120
|
+
HistoryCompatibility extends 'Forwards' ? 'summarizeHistory' : never
|
|
121
|
+
>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Configuration options for a SharedTree with write format 0.0.2
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
export interface SharedTreeOptions_0_0_2 {
|
|
128
|
+
/**
|
|
129
|
+
* Determines if the history is included in summaries.
|
|
130
|
+
*
|
|
131
|
+
* Warning: enabling history summarization incurs a permanent cost in the document. It is not possible to disable history summarization
|
|
132
|
+
* later once it has been enabled, and thus the history cannot be safely deleted.
|
|
133
|
+
*
|
|
134
|
+
* On 0.1.1 documents, due to current code limitations, this parameter is only impactful for newly created documents.
|
|
135
|
+
* `SharedTree`s which load existing documents will summarize history if and only if the loaded summary included history.
|
|
136
|
+
*
|
|
137
|
+
* The technical limitations here relate to clients with mixed versions collaborating.
|
|
138
|
+
* In the future we may allow modification of whether or not a particular document saves history, but only via a consensus mechanism.
|
|
139
|
+
* See the skipped test in SharedTreeFuzzTests.ts for more details on this issue.
|
|
140
|
+
* See docs/Breaking-Change-Migration for more details on the consensus scheme.
|
|
141
|
+
*/
|
|
142
|
+
summarizeHistory?: boolean;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Configuration options for a SharedTree with write format 0.1.1
|
|
147
|
+
* @public
|
|
148
|
+
*/
|
|
149
|
+
export interface SharedTreeOptions_0_1_1 {
|
|
150
|
+
/**
|
|
151
|
+
* Determines if the history is included in summaries and if edit chunks are uploaded when they are full.
|
|
152
|
+
*
|
|
153
|
+
* Warning: enabling history summarization incurs a permanent cost in the document. It is not possible to disable history summarization
|
|
154
|
+
* later once it has been enabled, and thus the history cannot be safely deleted.
|
|
155
|
+
*
|
|
156
|
+
* On 0.1.1 documents, due to current code limitations, this parameter is only impactful for newly created documents.
|
|
157
|
+
* `SharedTree`s which load existing documents will summarize history if and only if the loaded summary included history.
|
|
158
|
+
*
|
|
159
|
+
* The technical limitations here relate to clients with mixed versions collaborating.
|
|
160
|
+
* In the future we may allow modification of whether or not a particular document saves history, but only via a consensus mechanism.
|
|
161
|
+
* See the skipped test in SharedTreeFuzzTests.ts for more details on this issue.
|
|
162
|
+
* See docs/Breaking-Change-Migration for more details on the consensus scheme.
|
|
163
|
+
*/
|
|
164
|
+
summarizeHistory?: false | { uploadEditChunks: boolean };
|
|
165
|
+
/** a UUID that identifies the user of this tree; all node IDs generated by this tree will be associated with this UUID */
|
|
166
|
+
attributionId?: AttributionId;
|
|
167
|
+
}
|
|
91
168
|
|
|
92
169
|
/**
|
|
93
170
|
* Factory for SharedTree.
|
|
@@ -109,20 +186,20 @@ export class SharedTreeFactory implements IChannelFactory {
|
|
|
109
186
|
packageVersion: '0.1',
|
|
110
187
|
};
|
|
111
188
|
|
|
189
|
+
private readonly args: SharedTreeArgs;
|
|
190
|
+
|
|
112
191
|
/**
|
|
113
192
|
* Get a factory for SharedTree to register with the data store.
|
|
114
193
|
* @param writeFormat - Determines the format version the SharedTree will write ops and summaries in. See [the write format
|
|
115
194
|
* documentation](../docs/Write-Format.md) for more information.
|
|
116
|
-
* @param
|
|
117
|
-
* See the [breaking change migration documentation](docs/Breaking-Change-Migration) for more details on this scheme.
|
|
118
|
-
* @param expensiveValidation - Enables expensive asserts on SharedTree.
|
|
195
|
+
* @param options - Configuration options for this tree
|
|
119
196
|
* @returns A factory that creates `SharedTree`s and loads them from storage.
|
|
120
197
|
*/
|
|
121
|
-
constructor(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
198
|
+
constructor(...args: SharedTreeArgs<WriteFormat.v0_0_2>);
|
|
199
|
+
constructor(...args: SharedTreeArgs<WriteFormat.v0_1_1>);
|
|
200
|
+
constructor(...args: SharedTreeArgs) {
|
|
201
|
+
this.args = args;
|
|
202
|
+
}
|
|
126
203
|
|
|
127
204
|
/**
|
|
128
205
|
* {@inheritDoc @fluidframework/shared-object-base#ISharedObjectFactory."type"}
|
|
@@ -157,22 +234,22 @@ export class SharedTreeFactory implements IChannelFactory {
|
|
|
157
234
|
* @param runtime - data store runtime that owns the new SharedTree
|
|
158
235
|
* @param id - optional name for the SharedTree
|
|
159
236
|
*/
|
|
160
|
-
public create(runtime: IFluidDataStoreRuntime, id: string
|
|
161
|
-
this.expensiveValidation = expensiveValidation;
|
|
237
|
+
public create(runtime: IFluidDataStoreRuntime, id: string): SharedTree {
|
|
162
238
|
const sharedTree = this.createSharedTree(runtime, id);
|
|
163
239
|
sharedTree.initializeLocal();
|
|
164
240
|
return sharedTree;
|
|
165
241
|
}
|
|
166
242
|
|
|
167
243
|
private createSharedTree(runtime: IFluidDataStoreRuntime, id: string): SharedTree {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
244
|
+
const [writeFormat] = this.args;
|
|
245
|
+
switch (writeFormat) {
|
|
246
|
+
case WriteFormat.v0_0_2:
|
|
247
|
+
return new SharedTree(runtime, id, ...(this.args as SharedTreeArgs<WriteFormat.v0_0_2>));
|
|
248
|
+
case WriteFormat.v0_1_1:
|
|
249
|
+
return new SharedTree(runtime, id, ...(this.args as SharedTreeArgs<WriteFormat.v0_1_1>));
|
|
250
|
+
default:
|
|
251
|
+
fail('Unknown write format');
|
|
252
|
+
}
|
|
176
253
|
}
|
|
177
254
|
}
|
|
178
255
|
|
|
@@ -281,35 +358,56 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
281
358
|
|
|
282
359
|
/**
|
|
283
360
|
* Get a factory for SharedTree to register with the data store.
|
|
284
|
-
* @param summarizeHistory - Determines if the history is included in summaries and if edit chunks are uploaded when they are full.
|
|
285
|
-
*
|
|
286
|
-
* On 0.1.1 documents, due to current code limitations, this parameter is only impactful for newly created documents.
|
|
287
|
-
* `SharedTree`s which load existing documents will summarize history if and only if the loaded summary included history.
|
|
288
|
-
*
|
|
289
|
-
* The technical limitations here relate to clients with mixed versions collaborating.
|
|
290
|
-
* In the future we may allow modification of whether or not a particular document saves history, but only via a consensus mechanism.
|
|
291
|
-
* See the skipped test in SharedTreeFuzzTests.ts for more details on this issue.
|
|
292
|
-
* See docs/Breaking-Change-Migration for more details on the consensus scheme.
|
|
293
361
|
* @param writeFormat - Determines the format version the SharedTree will write ops and summaries in.
|
|
294
362
|
* This format may be updated to a newer (supported) version at runtime if a collaborating shared-tree
|
|
295
363
|
* that was initialized with a newer write version connects to the session. Care must be taken when changing this value,
|
|
296
364
|
* as a staged rollout must of occurred such that all collaborating clients must have the code to read at least the version
|
|
297
365
|
* written.
|
|
298
366
|
* See [the write format documentation](../docs/Write-Format.md) for more information.
|
|
367
|
+
* @param options - Configuration options for this tree
|
|
299
368
|
* @returns A factory that creates `SharedTree`s and loads them from storage.
|
|
300
369
|
*/
|
|
301
|
-
public static getFactory(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
370
|
+
public static getFactory(...args: SharedTreeArgs<WriteFormat.v0_0_2>): SharedTreeFactory;
|
|
371
|
+
|
|
372
|
+
public static getFactory(...args: SharedTreeArgs<WriteFormat.v0_1_1>): SharedTreeFactory;
|
|
373
|
+
|
|
374
|
+
public static getFactory(...args: SharedTreeArgs): SharedTreeFactory {
|
|
375
|
+
const [writeFormat] = args;
|
|
305
376
|
// On 0.1.1 documents, due to current code limitations, all clients MUST agree on the value of `summarizeHistory`.
|
|
306
377
|
// Note that this means staged rollout changing this value should not be attempted.
|
|
307
378
|
// It is possible to update shared-tree to correctly handle such a staged rollout, but that hasn't been implemented.
|
|
308
379
|
// See the skipped test in SharedTreeFuzzTests.ts for more details on this issue.
|
|
309
|
-
|
|
380
|
+
switch (writeFormat) {
|
|
381
|
+
case WriteFormat.v0_0_2:
|
|
382
|
+
return new SharedTreeFactory(...(args as SharedTreeArgs<WriteFormat.v0_0_2>));
|
|
383
|
+
case WriteFormat.v0_1_1:
|
|
384
|
+
return new SharedTreeFactory(...(args as SharedTreeArgs<WriteFormat.v0_1_1>));
|
|
385
|
+
default:
|
|
386
|
+
fail('Unknown write format');
|
|
387
|
+
}
|
|
310
388
|
}
|
|
311
389
|
|
|
312
|
-
|
|
390
|
+
/**
|
|
391
|
+
* The UUID used for attribution of nodes created by this SharedTree. All shared trees with a write format of 0.1.1 or
|
|
392
|
+
* greater have a unique attribution ID which may be configured in the constructor. All other shared trees (i.e. those
|
|
393
|
+
* with a write format of 0.0.2) use the nil UUID as their attribution ID.
|
|
394
|
+
* @public
|
|
395
|
+
*/
|
|
396
|
+
public get attributionId(): AttributionId {
|
|
397
|
+
switch (this.writeFormat) {
|
|
398
|
+
case WriteFormat.v0_0_2:
|
|
399
|
+
return nilUuid;
|
|
400
|
+
default: {
|
|
401
|
+
const { attributionId } = this.idCompressor;
|
|
402
|
+
if (attributionId === ghostSessionId) {
|
|
403
|
+
return nilUuid;
|
|
404
|
+
}
|
|
405
|
+
return attributionId;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private idCompressor: IdCompressor;
|
|
313
411
|
private readonly idNormalizer: NodeIdNormalizer<OpSpaceNodeId> & { tree: SharedTree } = {
|
|
314
412
|
tree: this,
|
|
315
413
|
get localSessionId() {
|
|
@@ -375,25 +473,44 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
375
473
|
private summarizeHistory: boolean;
|
|
376
474
|
private uploadEditChunks: boolean;
|
|
377
475
|
|
|
476
|
+
private getHistoryPolicy(options: SharedTreeOptions<WriteFormat, 'Forwards' | 'None'>): {
|
|
477
|
+
summarizeHistory: boolean;
|
|
478
|
+
uploadEditChunks: boolean;
|
|
479
|
+
} {
|
|
480
|
+
const noCompatOptions = options as SharedTreeOptions<WriteFormat, 'None'>;
|
|
481
|
+
return typeof noCompatOptions.summarizeHistory === 'object'
|
|
482
|
+
? {
|
|
483
|
+
summarizeHistory: true,
|
|
484
|
+
uploadEditChunks: noCompatOptions.summarizeHistory.uploadEditChunks,
|
|
485
|
+
}
|
|
486
|
+
: {
|
|
487
|
+
summarizeHistory: noCompatOptions.summarizeHistory ?? false,
|
|
488
|
+
uploadEditChunks: false,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
378
492
|
/**
|
|
379
|
-
* Create a new
|
|
493
|
+
* Create a new SharedTree.
|
|
380
494
|
* @param runtime - The runtime the SharedTree will be associated with
|
|
381
495
|
* @param id - Unique ID for the SharedTree
|
|
382
496
|
* @param writeFormat - Determines the format version the SharedTree will write ops and summaries in. See [the write format
|
|
383
497
|
* documentation](../docs/Write-Format.md) for more information.
|
|
384
|
-
* @param
|
|
385
|
-
* @param expensiveValidation - Enable expensive asserts.
|
|
498
|
+
* @param options - Configuration options for this tree
|
|
386
499
|
*/
|
|
500
|
+
public constructor(runtime: IFluidDataStoreRuntime, id: string, ...args: SharedTreeArgs<WriteFormat.v0_0_2>);
|
|
501
|
+
|
|
502
|
+
public constructor(runtime: IFluidDataStoreRuntime, id: string, ...args: SharedTreeArgs<WriteFormat.v0_1_1>);
|
|
503
|
+
|
|
387
504
|
public constructor(
|
|
388
505
|
runtime: IFluidDataStoreRuntime,
|
|
389
506
|
id: string,
|
|
390
507
|
private writeFormat: WriteFormat,
|
|
391
|
-
|
|
392
|
-
private readonly expensiveValidation = false
|
|
508
|
+
options: SharedTreeOptions<typeof writeFormat> = {}
|
|
393
509
|
) {
|
|
394
|
-
super(id, runtime, SharedTreeFactory.Attributes);
|
|
395
|
-
|
|
396
|
-
this.
|
|
510
|
+
super(id, runtime, SharedTreeFactory.Attributes, 'fluid_sharedTree_');
|
|
511
|
+
const historyPolicy = this.getHistoryPolicy(options);
|
|
512
|
+
this.summarizeHistory = historyPolicy.summarizeHistory;
|
|
513
|
+
this.uploadEditChunks = historyPolicy.uploadEditChunks;
|
|
397
514
|
|
|
398
515
|
// This code is somewhat duplicated from OldestClientObserver because it currently depends on the container runtime
|
|
399
516
|
// which SharedTree does not have access to.
|
|
@@ -412,13 +529,15 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
412
529
|
sharedTreeTelemetryProperties
|
|
413
530
|
);
|
|
414
531
|
|
|
532
|
+
const attributionId = (options as SharedTreeOptions<WriteFormat.v0_1_1>).attributionId;
|
|
533
|
+
this.idCompressor = new IdCompressor(createSessionId(), reservedIdCount, attributionId, this.logger);
|
|
415
534
|
const { editLog, cachingLogViewer } = this.initializeNewEditLogFromSummary(
|
|
416
535
|
{
|
|
417
536
|
editChunks: [],
|
|
418
537
|
editIds: [],
|
|
419
538
|
},
|
|
420
539
|
undefined,
|
|
421
|
-
this.idCompressor,
|
|
540
|
+
this.idCompressor,
|
|
422
541
|
this.processEditResult,
|
|
423
542
|
this.processSequencedEditResult,
|
|
424
543
|
WriteFormat.v0_1_1
|
|
@@ -429,6 +548,11 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
429
548
|
this.encoder_0_1_1 = new SharedTreeEncoder_0_1_1(this.summarizeHistory);
|
|
430
549
|
}
|
|
431
550
|
|
|
551
|
+
/**
|
|
552
|
+
* The write format version currently used by this `SharedTree`. This is always initialized to the write format
|
|
553
|
+
* passed to the tree's constructor, but it may automatically upgrade over time (e.g. when connected to another
|
|
554
|
+
* SharedTree with a higher write format, or when loading a summary with a higher write format).
|
|
555
|
+
*/
|
|
432
556
|
public getWriteFormat(): WriteFormat {
|
|
433
557
|
return this.writeFormat;
|
|
434
558
|
}
|
|
@@ -555,6 +679,26 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
555
679
|
return this.idCompressor.tryRecompress(id) as NodeId | undefined;
|
|
556
680
|
}
|
|
557
681
|
|
|
682
|
+
/**
|
|
683
|
+
* Returns the attribution ID associated with the SharedTree that generated the given node ID. This is generally only useful for clients
|
|
684
|
+
* with a write format of 0.1.1 or greater since older clients cannot be given an attribution ID and will always use the default
|
|
685
|
+
* `attributionId` of the tree.
|
|
686
|
+
* @public
|
|
687
|
+
*/
|
|
688
|
+
public attributeNodeId(id: NodeId): AttributionId {
|
|
689
|
+
switch (this.writeFormat) {
|
|
690
|
+
case WriteFormat.v0_0_2:
|
|
691
|
+
return nilUuid;
|
|
692
|
+
default: {
|
|
693
|
+
const attributionId = this.idCompressor.attributeId(id);
|
|
694
|
+
if (attributionId === ghostSessionId) {
|
|
695
|
+
return nilUuid;
|
|
696
|
+
}
|
|
697
|
+
return attributionId;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
558
702
|
/**
|
|
559
703
|
* @returns the edit history of the tree.
|
|
560
704
|
* @public
|
|
@@ -744,7 +888,10 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
744
888
|
let convertedSummary: SummaryContents;
|
|
745
889
|
switch (loadedSummaryVersion) {
|
|
746
890
|
case WriteFormat.v0_0_2:
|
|
747
|
-
convertedSummary = this.encoder_0_0_2.decodeSummary(
|
|
891
|
+
convertedSummary = this.encoder_0_0_2.decodeSummary(
|
|
892
|
+
summary as SharedTreeSummary_0_0_2,
|
|
893
|
+
this.attributionId
|
|
894
|
+
);
|
|
748
895
|
break;
|
|
749
896
|
case WriteFormat.v0_1_1: {
|
|
750
897
|
const typedSummary = summary as SharedTreeSummary;
|
|
@@ -756,7 +903,7 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
756
903
|
this.encoder_0_1_1 = new SharedTreeEncoder_0_1_1(this.summarizeHistory);
|
|
757
904
|
}
|
|
758
905
|
|
|
759
|
-
convertedSummary = this.encoder_0_1_1.decodeSummary(summary as SharedTreeSummary);
|
|
906
|
+
convertedSummary = this.encoder_0_1_1.decodeSummary(summary as SharedTreeSummary, this.attributionId);
|
|
760
907
|
break;
|
|
761
908
|
}
|
|
762
909
|
default:
|
|
@@ -944,9 +1091,12 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
944
1091
|
};
|
|
945
1092
|
this.cachingLogViewer.setMinimumSequenceNumber(typedMessage.minimumSequenceNumber);
|
|
946
1093
|
const op = typedMessage.contents;
|
|
1094
|
+
if (op.version === undefined) {
|
|
1095
|
+
// Back-compat: some legacy documents may contain trailing ops with an unstamped version; normalize them.
|
|
1096
|
+
(op as { version: WriteFormat | undefined }).version = WriteFormat.v0_0_2;
|
|
1097
|
+
}
|
|
947
1098
|
const { type, version } = op;
|
|
948
|
-
const
|
|
949
|
-
const sameVersion = resolvedVersion === this.writeFormat;
|
|
1099
|
+
const sameVersion = version === this.writeFormat;
|
|
950
1100
|
|
|
951
1101
|
// Edit and handle ops should only be processed if they're the same version as the tree write version.
|
|
952
1102
|
// Update ops should only be processed if they're not the same version.
|
|
@@ -979,7 +1129,7 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
979
1129
|
}
|
|
980
1130
|
} else if (type === SharedTreeOpType.Update) {
|
|
981
1131
|
this.processVersionUpdate(op.version);
|
|
982
|
-
} else if (compareSummaryFormatVersions(
|
|
1132
|
+
} else if (compareSummaryFormatVersions(version, this.writeFormat) === 1) {
|
|
983
1133
|
// An op version newer than our current version should not be received. If this happens, either an
|
|
984
1134
|
// incorrect op version has been written or an update op was skipped.
|
|
985
1135
|
const error = 'Newer op version received by a client that has yet to be updated.';
|
|
@@ -1115,7 +1265,7 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
1115
1265
|
this.interner = new MutableStringInterner([initialTree.definition]);
|
|
1116
1266
|
const oldIdCompressor = this.idCompressor;
|
|
1117
1267
|
// Create the IdCompressor that will be used after the upgrade
|
|
1118
|
-
const newIdCompressor = new IdCompressor(createSessionId(), reservedIdCount);
|
|
1268
|
+
const newIdCompressor = new IdCompressor(createSessionId(), reservedIdCount, this.attributionId, this.logger);
|
|
1119
1269
|
const newContext = getNodeIdContext(newIdCompressor);
|
|
1120
1270
|
// Generate all local IDs in the new compressor that were in the old compressor and preserve their UUIDs.
|
|
1121
1271
|
// This will allow the client to continue to use local IDs that were allocated pre-upgrade
|
|
@@ -1130,7 +1280,7 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
1130
1280
|
}
|
|
1131
1281
|
};
|
|
1132
1282
|
// Construct a temporary "ghost" compressor which is used to generate final IDs that will be consistent across all upgrading clients
|
|
1133
|
-
const ghostIdCompressor = new IdCompressor(ghostSessionId, reservedIdCount);
|
|
1283
|
+
const ghostIdCompressor = new IdCompressor(ghostSessionId, reservedIdCount);
|
|
1134
1284
|
const ghostContext = getNodeIdContext(ghostIdCompressor);
|
|
1135
1285
|
if (this.summarizeHistory) {
|
|
1136
1286
|
// All clients have the full history, and can therefore all "generate" the same final IDs for every ID in the history
|
package/src/SharedTreeEncoder.ts
CHANGED
|
@@ -134,18 +134,9 @@ export class SharedTreeEncoder_0_1_1 {
|
|
|
134
134
|
interner: StringInterner,
|
|
135
135
|
serializedIdCompressor: SerializedIdCompressorWithNoSession
|
|
136
136
|
): SharedTreeSummary {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return this.noHistorySummarizer(
|
|
141
|
-
edits,
|
|
142
|
-
currentView,
|
|
143
|
-
idContext,
|
|
144
|
-
idNormalizer,
|
|
145
|
-
interner,
|
|
146
|
-
serializedIdCompressor
|
|
147
|
-
);
|
|
148
|
-
}
|
|
137
|
+
return this.summarizeHistory
|
|
138
|
+
? this.fullHistorySummarizer(edits, currentView, idNormalizer, interner, serializedIdCompressor)
|
|
139
|
+
: this.noHistorySummarizer(edits, currentView, idContext, idNormalizer, interner, serializedIdCompressor);
|
|
149
140
|
}
|
|
150
141
|
|
|
151
142
|
/**
|
|
@@ -159,7 +150,7 @@ export class SharedTreeEncoder_0_1_1 {
|
|
|
159
150
|
idCompressor: serializedIdCompressor,
|
|
160
151
|
version,
|
|
161
152
|
}: SharedTreeSummary,
|
|
162
|
-
attributionId
|
|
153
|
+
attributionId: AttributionId
|
|
163
154
|
): SummaryContents {
|
|
164
155
|
assert(version === WriteFormat.v0_1_1, `Invalid summary version to decode: ${version}, expected: 0.1.1`);
|
|
165
156
|
assert(typeof editHistory === 'object', '0.1.1 summary encountered with non-object edit history.');
|
|
@@ -364,11 +355,9 @@ export class SharedTreeEncoder_0_0_2 {
|
|
|
364
355
|
currentView: RevisionView,
|
|
365
356
|
idConverter: NodeIdConverter
|
|
366
357
|
): SharedTreeSummary_0_0_2 {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return this.noHistorySummarizer(edits, currentView, idConverter);
|
|
371
|
-
}
|
|
358
|
+
return this.summarizeHistory
|
|
359
|
+
? this.fullHistorySummarizer(edits, currentView, idConverter)
|
|
360
|
+
: this.noHistorySummarizer(edits, currentView, idConverter);
|
|
372
361
|
}
|
|
373
362
|
|
|
374
363
|
/**
|
|
@@ -347,19 +347,17 @@ export class GenericTransaction {
|
|
|
347
347
|
}
|
|
348
348
|
const resolvedChange = resolutionResult.result;
|
|
349
349
|
const changeResult = this.policy.dispatchChange(this.state, resolvedChange);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
};
|
|
362
|
-
}
|
|
350
|
+
this.state = Result.isOk(changeResult)
|
|
351
|
+
? {
|
|
352
|
+
status: EditStatus.Applied,
|
|
353
|
+
view: changeResult.result,
|
|
354
|
+
changes: this.changes.concat(change),
|
|
355
|
+
steps: this.steps.concat({ resolvedChange, after: changeResult.result }),
|
|
356
|
+
}
|
|
357
|
+
: {
|
|
358
|
+
...this.state,
|
|
359
|
+
...changeResult.error,
|
|
360
|
+
};
|
|
363
361
|
return this;
|
|
364
362
|
}
|
|
365
363
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
|
7
7
|
|
|
8
|
+
import { ITelemetryLogger } from '@fluidframework/common-definitions';
|
|
8
9
|
import BTree from 'sorted-btree';
|
|
9
10
|
import {
|
|
10
11
|
assert,
|
|
@@ -384,7 +385,8 @@ export class IdCompressor {
|
|
|
384
385
|
public constructor(
|
|
385
386
|
public readonly localSessionId: SessionId,
|
|
386
387
|
public readonly reservedIdCount: number,
|
|
387
|
-
attributionId?: AttributionId
|
|
388
|
+
attributionId?: AttributionId,
|
|
389
|
+
private readonly logger?: ITelemetryLogger
|
|
388
390
|
) {
|
|
389
391
|
assert(reservedIdCount >= 0, 'reservedIdCount must be non-negative');
|
|
390
392
|
if (attributionId !== undefined) {
|
|
@@ -565,6 +567,7 @@ export class IdCompressor {
|
|
|
565
567
|
const finalizeCount = normalizedLastFinalizedLocal - newLastFinalizedLocal;
|
|
566
568
|
assert(finalizeCount >= 1, 'Cannot finalize an empty range.');
|
|
567
569
|
|
|
570
|
+
let eagerFinalIdCount = 0;
|
|
568
571
|
let initialClusterCount = 0;
|
|
569
572
|
let remainingCount = finalizeCount;
|
|
570
573
|
let newBaseUuid: NumericUuid | undefined;
|
|
@@ -577,6 +580,7 @@ export class IdCompressor {
|
|
|
577
580
|
Math.min(currentCluster.count + finalizeCount, currentCluster.capacity) -
|
|
578
581
|
1) as FinalCompressedId;
|
|
579
582
|
if (lastFinalInCluster > lastKnownFinal) {
|
|
583
|
+
eagerFinalIdCount = lastFinalInCluster - (lastKnownFinal + 1);
|
|
580
584
|
this.sessionIdNormalizer.addFinalIds(
|
|
581
585
|
(lastKnownFinal + 1) as FinalCompressedId,
|
|
582
586
|
lastFinalInCluster,
|
|
@@ -596,6 +600,7 @@ export class IdCompressor {
|
|
|
596
600
|
// The cluster is full but is the last in the list of clusters.
|
|
597
601
|
// This allows it to be expanded instead of allocating a new one.
|
|
598
602
|
const expansionAmount = this.newClusterCapacity + overflow;
|
|
603
|
+
const previousCapacity = currentCluster.capacity;
|
|
599
604
|
currentCluster.capacity += expansionAmount;
|
|
600
605
|
this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId + expansionAmount) as FinalCompressedId;
|
|
601
606
|
assert(
|
|
@@ -617,6 +622,13 @@ export class IdCompressor {
|
|
|
617
622
|
const lastFinalizedFinal = (currentBaseFinalId + currentCluster.count - 1) as FinalCompressedId;
|
|
618
623
|
const finalPivot = (lastFinalizedFinal - overflow + 1) as FinalCompressedId;
|
|
619
624
|
this.sessionIdNormalizer.addFinalIds(finalPivot, lastFinalizedFinal, currentCluster);
|
|
625
|
+
this.logger?.sendTelemetryEvent({
|
|
626
|
+
eventName: 'IdCompressor:ClusterExpansion',
|
|
627
|
+
sessionId: this.localSessionId,
|
|
628
|
+
previousCapacity,
|
|
629
|
+
newCapacity: currentCluster.capacity,
|
|
630
|
+
overflow,
|
|
631
|
+
});
|
|
620
632
|
}
|
|
621
633
|
}
|
|
622
634
|
} else {
|
|
@@ -625,10 +637,18 @@ export class IdCompressor {
|
|
|
625
637
|
newBaseUuid = incrementUuid(currentCluster.baseUuid, currentCluster.capacity);
|
|
626
638
|
currentCluster.count += remainingCapacity;
|
|
627
639
|
remainingCount -= remainingCapacity;
|
|
640
|
+
this.logger?.sendTelemetryEvent({
|
|
641
|
+
eventName: 'IdCompressor:OverfilledCluster',
|
|
642
|
+
sessionId: this.localSessionId,
|
|
643
|
+
});
|
|
628
644
|
}
|
|
629
645
|
} else {
|
|
630
646
|
// Session has never made a cluster, form a new one with the session UUID as the baseUuid
|
|
631
647
|
newBaseUuid = session.sessionUuid;
|
|
648
|
+
this.logger?.sendTelemetryEvent({
|
|
649
|
+
eventName: 'IdCompressor:FirstCluster',
|
|
650
|
+
sessionId: this.localSessionId,
|
|
651
|
+
});
|
|
632
652
|
}
|
|
633
653
|
|
|
634
654
|
// Finalizing a range results in one of three cases:
|
|
@@ -652,9 +672,10 @@ export class IdCompressor {
|
|
|
652
672
|
}
|
|
653
673
|
|
|
654
674
|
newBaseFinalId = this.nextClusterBaseFinalId;
|
|
675
|
+
const newCapacity = Math.max(this.newClusterCapacity, remainingCount);
|
|
655
676
|
newCluster = {
|
|
656
677
|
baseUuid: newBaseUuid,
|
|
657
|
-
capacity:
|
|
678
|
+
capacity: newCapacity,
|
|
658
679
|
count: remainingCount,
|
|
659
680
|
session,
|
|
660
681
|
};
|
|
@@ -663,6 +684,12 @@ export class IdCompressor {
|
|
|
663
684
|
localIdPivot = (newFirstFinalizedLocal - usedCapacity) as LocalCompressedId;
|
|
664
685
|
|
|
665
686
|
if (isLocal) {
|
|
687
|
+
this.logger?.sendTelemetryEvent({
|
|
688
|
+
eventName: 'IdCompressor:NewCluster',
|
|
689
|
+
sessionId: this.localSessionId,
|
|
690
|
+
clusterCapacity: newCapacity,
|
|
691
|
+
clusterCount: remainingCount,
|
|
692
|
+
});
|
|
666
693
|
const lastFinalizedFinal = (newBaseFinalId + newCluster.count - 1) as FinalCompressedId;
|
|
667
694
|
this.sessionIdNormalizer.addFinalIds(newBaseFinalId, lastFinalizedFinal, newCluster);
|
|
668
695
|
}
|
|
@@ -779,6 +806,16 @@ export class IdCompressor {
|
|
|
779
806
|
}
|
|
780
807
|
}
|
|
781
808
|
|
|
809
|
+
if (isLocal) {
|
|
810
|
+
this.logger?.sendTelemetryEvent({
|
|
811
|
+
eventName: 'IdCompressor:IdCompressorStatus',
|
|
812
|
+
eagerFinalIdCount,
|
|
813
|
+
localIdCount: remainingCount,
|
|
814
|
+
overridesCount: overrides?.length ?? 0,
|
|
815
|
+
sessionId: this.localSessionId,
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
782
819
|
session.lastFinalizedLocalId = newLastFinalizedLocal;
|
|
783
820
|
}
|
|
784
821
|
|
|
@@ -1014,11 +1051,9 @@ export class IdCompressor {
|
|
|
1014
1051
|
// `localOverrides`s. Otherwise, it is a sequential allocation from the session UUID and can simply be negated and
|
|
1015
1052
|
// added to that UUID to obtain the stable ID associated with it.
|
|
1016
1053
|
const localOverride = this.localOverrides?.get(id);
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
return stableIdFromNumericUuid(this.localSession.sessionUuid, idOffset - 1);
|
|
1021
|
-
}
|
|
1054
|
+
return localOverride !== undefined
|
|
1055
|
+
? localOverride
|
|
1056
|
+
: stableIdFromNumericUuid(this.localSession.sessionUuid, idOffset - 1);
|
|
1022
1057
|
}
|
|
1023
1058
|
}
|
|
1024
1059
|
|
|
@@ -1056,14 +1091,10 @@ export class IdCompressor {
|
|
|
1056
1091
|
const [key, compressionMapping] = closestMatch;
|
|
1057
1092
|
if (!IdCompressor.isClusterInfo(compressionMapping)) {
|
|
1058
1093
|
if (key === inversionKey) {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
compressionMapping.associatedLocalId ??
|
|
1064
|
-
(compressionMapping.originalOverridingFinal as SessionSpaceCompressedId)
|
|
1065
|
-
);
|
|
1066
|
-
}
|
|
1094
|
+
return IdCompressor.isUnfinalizedOverride(compressionMapping)
|
|
1095
|
+
? compressionMapping
|
|
1096
|
+
: compressionMapping.associatedLocalId ??
|
|
1097
|
+
(compressionMapping.originalOverridingFinal as SessionSpaceCompressedId);
|
|
1067
1098
|
}
|
|
1068
1099
|
} else {
|
|
1069
1100
|
if (!isStable) {
|
package/src/index.ts
CHANGED