@fluid-experimental/tree 0.58.3000-61081 → 0.59.2000-61729
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/README.md +156 -43
- package/dist/ChangeTypes.d.ts +9 -14
- package/dist/ChangeTypes.d.ts.map +1 -1
- package/dist/ChangeTypes.js.map +1 -1
- package/dist/Checkout.d.ts.map +1 -1
- package/dist/Checkout.js +3 -2
- package/dist/Checkout.js.map +1 -1
- package/dist/LogViewer.d.ts +2 -3
- package/dist/LogViewer.d.ts.map +1 -1
- package/dist/LogViewer.js +5 -4
- package/dist/LogViewer.js.map +1 -1
- package/dist/NodeIdUtilities.d.ts +26 -11
- package/dist/NodeIdUtilities.d.ts.map +1 -1
- package/dist/NodeIdUtilities.js.map +1 -1
- package/dist/SharedTree.d.ts +61 -22
- package/dist/SharedTree.d.ts.map +1 -1
- package/dist/SharedTree.js +75 -30
- package/dist/SharedTree.js.map +1 -1
- package/dist/Transaction.d.ts +22 -4
- package/dist/Transaction.d.ts.map +1 -1
- package/dist/Transaction.js +27 -11
- package/dist/Transaction.js.map +1 -1
- package/dist/TransactionInternal.d.ts +3 -6
- package/dist/TransactionInternal.d.ts.map +1 -1
- package/dist/TransactionInternal.js +8 -4
- package/dist/TransactionInternal.js.map +1 -1
- package/dist/TreeCompressor.d.ts +0 -1
- package/dist/TreeCompressor.d.ts.map +1 -1
- package/dist/TreeCompressor.js +31 -26
- package/dist/TreeCompressor.js.map +1 -1
- package/dist/TreeView.d.ts.map +1 -1
- package/dist/TreeView.js +3 -2
- package/dist/TreeView.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/docs/Write-Format.md +19 -0
- package/lib/ChangeTypes.d.ts +9 -14
- package/lib/ChangeTypes.d.ts.map +1 -1
- package/lib/ChangeTypes.js.map +1 -1
- package/lib/Checkout.d.ts.map +1 -1
- package/lib/Checkout.js +3 -2
- package/lib/Checkout.js.map +1 -1
- package/lib/LogViewer.d.ts +2 -3
- package/lib/LogViewer.d.ts.map +1 -1
- package/lib/LogViewer.js +5 -4
- package/lib/LogViewer.js.map +1 -1
- package/lib/NodeIdUtilities.d.ts +26 -11
- package/lib/NodeIdUtilities.d.ts.map +1 -1
- package/lib/NodeIdUtilities.js.map +1 -1
- package/lib/SharedTree.d.ts +61 -22
- package/lib/SharedTree.d.ts.map +1 -1
- package/lib/SharedTree.js +75 -30
- package/lib/SharedTree.js.map +1 -1
- package/lib/Transaction.d.ts +22 -4
- package/lib/Transaction.d.ts.map +1 -1
- package/lib/Transaction.js +26 -10
- package/lib/Transaction.js.map +1 -1
- package/lib/TransactionInternal.d.ts +3 -6
- package/lib/TransactionInternal.d.ts.map +1 -1
- package/lib/TransactionInternal.js +8 -4
- package/lib/TransactionInternal.js.map +1 -1
- package/lib/TreeCompressor.d.ts +0 -1
- package/lib/TreeCompressor.d.ts.map +1 -1
- package/lib/TreeCompressor.js +31 -26
- package/lib/TreeCompressor.js.map +1 -1
- package/lib/TreeView.d.ts.map +1 -1
- package/lib/TreeView.js +3 -2
- package/lib/TreeView.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/LogViewer.tests.js +13 -10
- package/lib/test/LogViewer.tests.js.map +1 -1
- package/lib/test/Transaction.tests.js +36 -2
- package/lib/test/Transaction.tests.js.map +1 -1
- package/lib/test/TreeView.tests.js +29 -0
- package/lib/test/TreeView.tests.js.map +1 -1
- package/lib/test/Virtualization.tests.js +1 -2
- package/lib/test/Virtualization.tests.js.map +1 -1
- package/lib/test/fuzz/SharedTreeFuzzTests.d.ts.map +1 -1
- package/lib/test/fuzz/SharedTreeFuzzTests.js +19 -2
- package/lib/test/fuzz/SharedTreeFuzzTests.js.map +1 -1
- package/lib/test/utilities/SharedTreeVersioningTests.d.ts.map +1 -1
- package/lib/test/utilities/SharedTreeVersioningTests.js +26 -1
- package/lib/test/utilities/SharedTreeVersioningTests.js.map +1 -1
- package/package.json +18 -18
- package/src/ChangeTypes.ts +9 -14
- package/src/Checkout.ts +3 -3
- package/src/LogViewer.ts +4 -4
- package/src/NodeIdUtilities.ts +26 -11
- package/src/SharedTree.ts +81 -28
- package/src/Transaction.ts +39 -13
- package/src/TransactionInternal.ts +9 -10
- package/src/TreeCompressor.ts +31 -40
- package/src/TreeView.ts +3 -2
- package/src/index.ts +0 -1
- package/docs/Future.md +0 -155
package/src/SharedTree.ts
CHANGED
|
@@ -110,13 +110,10 @@ export class SharedTreeFactory implements IChannelFactory {
|
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* Get a factory for SharedTree to register with the data store.
|
|
113
|
-
* @param writeFormat - Determines the format version the SharedTree will write summaries in.
|
|
114
|
-
*
|
|
115
|
-
* that was initialized with a newer write version connects to the session. Care must be taken when changing this value,
|
|
116
|
-
* as a staged rollout must have occurred such that all collaborating clients must have the code to read at least the version
|
|
117
|
-
* written.
|
|
113
|
+
* @param writeFormat - Determines the format version the SharedTree will write ops and summaries in. See [the write format
|
|
114
|
+
* documentation](../docs/Write-Format.md) for more information.
|
|
118
115
|
* @param summarizeHistory - Determines if the history is included in summaries and if edit chunks are uploaded when they are full.
|
|
119
|
-
* See docs/Breaking-Change-Migration for more details on this scheme.
|
|
116
|
+
* See the [breaking change migration documentation](docs/Breaking-Change-Migration) for more details on this scheme.
|
|
120
117
|
* @param expensiveValidation - Enables expensive asserts on SharedTree.
|
|
121
118
|
* @returns A factory that creates `SharedTree`s and loads them from storage.
|
|
122
119
|
*/
|
|
@@ -270,7 +267,7 @@ export type SequencedEditAppliedHandler = (args: SequencedEditAppliedEventArgume
|
|
|
270
267
|
const sharedTreeTelemetryProperties: ITelemetryLoggerPropertyBags = { all: { isSharedTreeEvent: true } };
|
|
271
268
|
|
|
272
269
|
/**
|
|
273
|
-
* A distributed tree.
|
|
270
|
+
* A [distributed tree](../Readme.md).
|
|
274
271
|
* @public
|
|
275
272
|
*/
|
|
276
273
|
export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeIdContext {
|
|
@@ -284,6 +281,7 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
284
281
|
/**
|
|
285
282
|
* Get a factory for SharedTree to register with the data store.
|
|
286
283
|
* @param summarizeHistory - Determines if the history is included in summaries and if edit chunks are uploaded when they are full.
|
|
284
|
+
*
|
|
287
285
|
* On 0.1.1 documents, due to current code limitations, this parameter is only impactful for newly created documents.
|
|
288
286
|
* `SharedTree`s which load existing documents will summarize history if and only if the loaded summary included history.
|
|
289
287
|
*
|
|
@@ -291,17 +289,22 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
291
289
|
* In the future we may allow modification of whether or not a particular document saves history, but only via a consensus mechanism.
|
|
292
290
|
* See the skipped test in SharedTreeFuzzTests.ts for more details on this issue.
|
|
293
291
|
* See docs/Breaking-Change-Migration for more details on the consensus scheme.
|
|
294
|
-
* @param writeFormat - Determines the format version the SharedTree will write summaries in.
|
|
292
|
+
* @param writeFormat - Determines the format version the SharedTree will write ops and summaries in.
|
|
295
293
|
* This format may be updated to a newer (supported) version at runtime if a collaborating shared-tree
|
|
296
294
|
* that was initialized with a newer write version connects to the session. Care must be taken when changing this value,
|
|
297
295
|
* as a staged rollout must of occurred such that all collaborating clients must have the code to read at least the version
|
|
298
296
|
* written.
|
|
297
|
+
* See [the write format documentation](../docs/Write-Format.md) for more information.
|
|
299
298
|
* @returns A factory that creates `SharedTree`s and loads them from storage.
|
|
300
299
|
*/
|
|
301
300
|
public static getFactory(
|
|
302
301
|
writeFormat: WriteFormat,
|
|
303
302
|
summarizeHistory: false | { uploadEditChunks: boolean } = false
|
|
304
303
|
): SharedTreeFactory {
|
|
304
|
+
// On 0.1.1 documents, due to current code limitations, all clients MUST agree on the value of `summarizeHistory`.
|
|
305
|
+
// Note that this means staged rollout changing this value should not be attempted.
|
|
306
|
+
// It is possible to update shared-tree to correctly handle such a staged rollout, but that hasn't been implemented.
|
|
307
|
+
// See the skipped test in SharedTreeFuzzTests.ts for more details on this issue.
|
|
305
308
|
return new SharedTreeFactory(writeFormat, summarizeHistory);
|
|
306
309
|
}
|
|
307
310
|
|
|
@@ -368,8 +371,6 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
368
371
|
this.emit(SharedTreeEvent.SequencedEditApplied, eventArguments);
|
|
369
372
|
};
|
|
370
373
|
|
|
371
|
-
public readonly transactionFactory = TransactionInternal.factory;
|
|
372
|
-
|
|
373
374
|
private summarizeHistory: boolean;
|
|
374
375
|
private uploadEditChunks: boolean;
|
|
375
376
|
|
|
@@ -377,7 +378,8 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
377
378
|
* Create a new SharedTreeFactory.
|
|
378
379
|
* @param runtime - The runtime the SharedTree will be associated with
|
|
379
380
|
* @param id - Unique ID for the SharedTree
|
|
380
|
-
* @param writeFormat - Determines the format version the SharedTree will write summaries in.
|
|
381
|
+
* @param writeFormat - Determines the format version the SharedTree will write ops and summaries in. See [the write format
|
|
382
|
+
* documentation](../docs/Write-Format.md) for more information.
|
|
381
383
|
* @param summarizeHistory - Determines if the history is included in summaries and if edit chunks are uploaded when they are full.
|
|
382
384
|
* @param expensiveValidation - Enable expensive asserts.
|
|
383
385
|
*/
|
|
@@ -490,31 +492,64 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
490
492
|
}
|
|
491
493
|
|
|
492
494
|
/**
|
|
493
|
-
*
|
|
495
|
+
* Generates a node identifier.
|
|
496
|
+
* The returned IDs may be used as the identifier of a node in the SharedTree.
|
|
497
|
+
* `NodeId`s are *always* unique and stable within the scope of the tree and session that generated them. They are *not* unique within
|
|
498
|
+
* a Fluid container, and *cannot* be compared across instances of a SharedTree. They are *not* stable across sessions/lifetimes of a
|
|
499
|
+
* SharedTree, and *cannot* be persisted (e.g. stored in payloads, uploaded in blobs, etc.). If stable persistence is needed,
|
|
500
|
+
* NodeIdConverter.convertToStableNodeId may be used to return a corresponding UUID that is globally unique and stable.
|
|
501
|
+
* @param override - if supplied, calls to `convertToStableNodeId` using the returned node ID will return the override instead of
|
|
502
|
+
* the UUID. Calls to `generateNodeId` with the same override always return the same ID. Performance note: passing an override string
|
|
503
|
+
* incurs a storage cost that is significantly higher that a node ID without one, and should be avoided if possible.
|
|
494
504
|
* @public
|
|
495
505
|
*/
|
|
496
506
|
public generateNodeId(override?: string): NodeId {
|
|
497
507
|
return this.idCompressor.generateCompressedId(override) as NodeId;
|
|
498
508
|
}
|
|
499
509
|
|
|
500
|
-
/**
|
|
510
|
+
/**
|
|
511
|
+
* Given a NodeId, returns the corresponding stable ID or throws if the supplied node ID was not generated with this tree (`NodeId`s
|
|
512
|
+
* may not be used across SharedTree instances, see `generateNodeId` for more).
|
|
513
|
+
* The returned value will be a UUID, unless the creation of `id` used an override string (see `generateNodeId` for more).
|
|
514
|
+
* The result is safe to persist and re-use across `SharedTree` instances, unlike `NodeId`.
|
|
515
|
+
* @public
|
|
516
|
+
*/
|
|
501
517
|
public convertToStableNodeId(id: NodeId): StableNodeId {
|
|
502
518
|
return (this.idCompressor.tryDecompress(id) as StableNodeId) ?? fail('Node id is not known to this SharedTree');
|
|
503
519
|
}
|
|
504
520
|
|
|
505
|
-
/**
|
|
521
|
+
/**
|
|
522
|
+
* Given a NodeId, attempt to return the corresponding stable ID.
|
|
523
|
+
* The returned value will be a UUID, unless the creation of `id` used an override string (see `generateNodeId` for more).
|
|
524
|
+
* The returned stable ID is undefined if `id` was never created with this SharedTree. If a stable ID is returned, this does not imply
|
|
525
|
+
* that there is a node with `id` in the current revision of the tree, only that `id` was at some point generated by some instance of
|
|
526
|
+
* this tree.
|
|
527
|
+
* @public
|
|
528
|
+
*/
|
|
506
529
|
public tryConvertToStableNodeId(id: NodeId): StableNodeId | undefined {
|
|
507
530
|
return this.idCompressor.tryDecompress(id) as StableNodeId | undefined;
|
|
508
531
|
}
|
|
509
532
|
|
|
510
|
-
/**
|
|
533
|
+
/**
|
|
534
|
+
* Given a stable ID, return the corresponding NodeId or throws if the supplied stable ID was never generated with this tree, either
|
|
535
|
+
* as a UUID corresponding to a `NodeId` or as an override passed to `generateNodeId`.
|
|
536
|
+
* If a stable ID is returned, this does not imply that there is a node with `id` in the current revision of the tree, only that
|
|
537
|
+
* `id` was at some point generated by an instance of this SharedTree.
|
|
538
|
+
* @public
|
|
539
|
+
*/
|
|
511
540
|
public convertToNodeId(id: StableNodeId): NodeId {
|
|
512
541
|
return (
|
|
513
542
|
(this.idCompressor.tryRecompress(id) as NodeId) ?? fail('Stable node id is not known to this SharedTree')
|
|
514
543
|
);
|
|
515
544
|
}
|
|
516
545
|
|
|
517
|
-
/**
|
|
546
|
+
/**
|
|
547
|
+
* Given a stable ID, return the corresponding NodeId or return undefined if the supplied stable ID was never generated with this tree,
|
|
548
|
+
* either as a UUID corresponding to a `NodeId` or as an override passed to `generateNodeId`.
|
|
549
|
+
* If a stable ID is returned, this does not imply that there is a node with `id` in the current revision of the tree, only that
|
|
550
|
+
* `id` was at some point generated by an instance of this SharedTree.
|
|
551
|
+
* @public
|
|
552
|
+
*/
|
|
518
553
|
public tryConvertToNodeId(id: StableNodeId): NodeId | undefined {
|
|
519
554
|
return this.idCompressor.tryRecompress(id) as NodeId | undefined;
|
|
520
555
|
}
|
|
@@ -835,7 +870,6 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
835
870
|
this.expensiveValidation,
|
|
836
871
|
editStatusCallback,
|
|
837
872
|
sequencedEditResultCallback,
|
|
838
|
-
this.transactionFactory,
|
|
839
873
|
0
|
|
840
874
|
);
|
|
841
875
|
|
|
@@ -863,7 +897,7 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
863
897
|
}
|
|
864
898
|
|
|
865
899
|
/**
|
|
866
|
-
* Compares this shared tree to another for equality. Should only be used for correctness testing.
|
|
900
|
+
* Compares this shared tree to another for equality. Should only be used for internal correctness testing.
|
|
867
901
|
*
|
|
868
902
|
* Equality means that the histories as captured by the EditLogs are equivalent.
|
|
869
903
|
*
|
|
@@ -942,7 +976,9 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
942
976
|
}
|
|
943
977
|
// TODO: This cast can be removed on typescript 4.6
|
|
944
978
|
const edit = this.parseSequencedEdit(op as SharedTreeEditOp | SharedTreeEditOp_0_0_2);
|
|
945
|
-
|
|
979
|
+
if (op.version === WriteFormat.v0_1_1) {
|
|
980
|
+
this.internStringsFromEdit(edit);
|
|
981
|
+
}
|
|
946
982
|
this.processSequencedEdit(edit, typedMessage);
|
|
947
983
|
}
|
|
948
984
|
} else if (type === SharedTreeOpType.Update) {
|
|
@@ -1083,9 +1119,8 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
1083
1119
|
}
|
|
1084
1120
|
|
|
1085
1121
|
private upgradeFrom_0_0_2_to_0_1_1(): void {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1122
|
+
// Reset the string interner, re-populate only with information that there is consensus on
|
|
1123
|
+
this.interner = new MutableStringInterner([initialTree.definition]);
|
|
1089
1124
|
const oldIdCompressor = this.idCompressor;
|
|
1090
1125
|
// Create the IdCompressor that will be used after the upgrade
|
|
1091
1126
|
const newIdCompressor = new IdCompressor(createSessionId(), reservedIdCount); // TODO: attribution info
|
|
@@ -1109,11 +1144,20 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
1109
1144
|
// All clients have the full history, and can therefore all "generate" the same final IDs for every ID in the history
|
|
1110
1145
|
// via the ghost compressor.
|
|
1111
1146
|
unifyHistoricalIds(ghostContext);
|
|
1147
|
+
// The same logic applies to string interning, so intern all the strings in the history (superset of those in the current view)
|
|
1148
|
+
for (let i = 0; i < this.editLog.numberOfSequencedEdits; i++) {
|
|
1149
|
+
this.internStringsFromEdit(this.editsInternal.getEditInSessionAtIndex(i));
|
|
1150
|
+
}
|
|
1112
1151
|
} else {
|
|
1113
1152
|
// Clients do not have the full history, but all share the same current view (sequenced). They can all finalize the same final
|
|
1114
1153
|
// IDs for every ID in the view via the ghost compressor.
|
|
1154
|
+
// The same logic applies for the string interner.
|
|
1115
1155
|
for (const node of this.logViewer.getRevisionViewInSession(this.editLog.numberOfSequencedEdits)) {
|
|
1116
1156
|
ghostContext.generateNodeId(this.convertToStableNodeId(node.identifier));
|
|
1157
|
+
this.interner.getOrCreateInternedId(node.definition);
|
|
1158
|
+
for (const label of [...node.traits.keys()].sort()) {
|
|
1159
|
+
this.interner.getOrCreateInternedId(label);
|
|
1160
|
+
}
|
|
1117
1161
|
}
|
|
1118
1162
|
// Every node in this client's history can simply be generated in the new compressor as well, preserving the UUID
|
|
1119
1163
|
unifyHistoricalIds(newContext);
|
|
@@ -1124,12 +1168,16 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
1124
1168
|
}
|
|
1125
1169
|
|
|
1126
1170
|
/**
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1171
|
+
* Applies a set of changes to this tree. The result will be reflected in `SharedTree.currentView`.
|
|
1172
|
+
* This method does not allow for snapshot isolation, as the changes are always applied to the most recent revision.
|
|
1173
|
+
* If it is desireable to read from and apply changes to a fixed view that does not change when remote changes arrive, `Checkout`
|
|
1174
|
+
* should be used instead.
|
|
1175
|
+
* @public
|
|
1131
1176
|
*/
|
|
1132
|
-
public applyEdit(...changes:
|
|
1177
|
+
public applyEdit(...changes: Change[]): Edit<unknown>;
|
|
1178
|
+
public applyEdit(changes: Change[]): Edit<unknown>;
|
|
1179
|
+
public applyEdit(headOrChanges: Change | Change[], ...tail: Change[]): Edit<unknown> {
|
|
1180
|
+
const changes = Array.isArray(headOrChanges) ? headOrChanges : [headOrChanges, ...tail];
|
|
1133
1181
|
const id = newEditId();
|
|
1134
1182
|
const internalEdit: Edit<ChangeInternal> = {
|
|
1135
1183
|
id,
|
|
@@ -1141,6 +1189,10 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
1141
1189
|
}
|
|
1142
1190
|
|
|
1143
1191
|
/**
|
|
1192
|
+
* Applies a set of internal changes to this tree. The result will be reflected in `SharedTree.currentView`.
|
|
1193
|
+
* External users should use one of the more specialized functions, like `applyEdit` which handles constructing the actual `Edit`
|
|
1194
|
+
* and uses public Change types.
|
|
1195
|
+
* This is exposed for internal use only.
|
|
1144
1196
|
* @internal
|
|
1145
1197
|
*/
|
|
1146
1198
|
public applyEditInternal(editOrChanges: Edit<ChangeInternal> | readonly ChangeInternal[]): Edit<ChangeInternal> {
|
|
@@ -1157,7 +1209,8 @@ export class SharedTree extends SharedObject<ISharedTreeEvents> implements NodeI
|
|
|
1157
1209
|
}
|
|
1158
1210
|
|
|
1159
1211
|
/**
|
|
1160
|
-
*
|
|
1212
|
+
* Converts a public Change type to an internal representation.
|
|
1213
|
+
* This is exposed for internal use only.
|
|
1161
1214
|
* @internal
|
|
1162
1215
|
*/
|
|
1163
1216
|
public internalizeChange(change: Change): ChangeInternal {
|
package/src/Transaction.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { IErrorEvent } from '@fluidframework/common-definitions';
|
|
7
|
+
import { TypedEventEmitter } from '@fluidframework/common-utils';
|
|
6
8
|
import { ChangeInternal, Edit, EditStatus } from './persisted-types';
|
|
7
9
|
import { newEditId } from './EditUtilities';
|
|
8
10
|
import { TreeView } from './TreeView';
|
|
@@ -11,16 +13,32 @@ import { SharedTree } from './SharedTree';
|
|
|
11
13
|
import { GenericTransaction, TransactionInternal } from './TransactionInternal';
|
|
12
14
|
import { CachingLogViewer } from './LogViewer';
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* An event emitted by a `Transaction` to indicate a state change. See {@link TransactionEvents} for event argument information.
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export enum TransactionEvent {
|
|
21
|
+
/**
|
|
22
|
+
* `currentView` has changed from `before` to `after`
|
|
23
|
+
*/
|
|
24
|
+
ViewChange = 'viewChange',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Events which may be emitted by `Transaction`
|
|
29
|
+
*/
|
|
30
|
+
export interface TransactionEvents extends IErrorEvent {
|
|
31
|
+
(event: TransactionEvent.ViewChange, listener: (before: TreeView, after: TreeView) => void);
|
|
32
|
+
}
|
|
33
|
+
|
|
14
34
|
/**
|
|
15
35
|
* Buffers changes to be applied to an isolated view of a `SharedTree` over time before applying them directly to the tree itself as a
|
|
16
36
|
* single edit
|
|
17
37
|
*/
|
|
18
|
-
export class Transaction {
|
|
38
|
+
export class Transaction extends TypedEventEmitter<TransactionEvents> {
|
|
19
39
|
/** The view of the tree when this transaction was created */
|
|
20
40
|
public readonly startingView: TreeView;
|
|
21
|
-
|
|
22
41
|
private readonly transaction: GenericTransaction;
|
|
23
|
-
private open: boolean = true;
|
|
24
42
|
|
|
25
43
|
/**
|
|
26
44
|
* Create a new transaction over the given tree. The tree's `currentView` at this time will become the `startingView` for this
|
|
@@ -28,6 +46,7 @@ export class Transaction {
|
|
|
28
46
|
* @param tree - the `SharedTree` that this transaction applies changes to
|
|
29
47
|
*/
|
|
30
48
|
public constructor(public readonly tree: SharedTree) {
|
|
49
|
+
super();
|
|
31
50
|
const { currentView } = tree;
|
|
32
51
|
this.transaction = new GenericTransaction(currentView, new TransactionInternal.Policy());
|
|
33
52
|
this.startingView = currentView;
|
|
@@ -38,7 +57,7 @@ export class Transaction {
|
|
|
38
57
|
* be automatically closed by a change in this transaction failing to apply (see `applyChange()`).
|
|
39
58
|
*/
|
|
40
59
|
public get isOpen(): boolean {
|
|
41
|
-
return this.
|
|
60
|
+
return this.transaction.isOpen && this.status === EditStatus.Applied;
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
/**
|
|
@@ -59,18 +78,26 @@ export class Transaction {
|
|
|
59
78
|
/**
|
|
60
79
|
* Attempt to apply a sequence of changes in this transaction. The `currentView` will be updated to reflect the new tree state after all
|
|
61
80
|
* applied changes. If any change fails to apply, the remaining changes will be ignored and this transaction will be automatically
|
|
62
|
-
* closed (see `isOpen`). If this transaction is already closed, this method has no effect.
|
|
81
|
+
* closed (see `isOpen`). If this transaction is already closed, this method has no effect. This method will emit a
|
|
82
|
+
* `TransactionEvent.ViewChange` event at most once per call.
|
|
63
83
|
* @param changes - the changes to apply
|
|
64
84
|
* @returns either the `EditStatus` of the given changes or the `EditStatus` of the last change before the transaction was closed
|
|
65
85
|
*/
|
|
66
|
-
public apply(
|
|
86
|
+
public apply(...changes: Change[]): EditStatus;
|
|
67
87
|
public apply(changes: Change[]): EditStatus;
|
|
68
|
-
public apply(
|
|
88
|
+
public apply(headOrChanges: Change | Change[], ...tail: Change[]): EditStatus {
|
|
69
89
|
if (this.isOpen) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
const changes = Array.isArray(headOrChanges) ? headOrChanges : [headOrChanges, ...tail];
|
|
91
|
+
if (changes.length > 0) {
|
|
92
|
+
const previousView = this.currentView;
|
|
93
|
+
this.transaction.applyChanges(changes.map((c) => this.tree.internalizeChange(c)));
|
|
94
|
+
if (
|
|
95
|
+
this.listenerCount(TransactionEvent.ViewChange) > 0 &&
|
|
96
|
+
!previousView.hasEqualForest(this.currentView)
|
|
97
|
+
) {
|
|
98
|
+
this.emit(TransactionEvent.ViewChange, previousView, this.currentView);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
74
101
|
}
|
|
75
102
|
return this.status;
|
|
76
103
|
}
|
|
@@ -81,14 +108,13 @@ export class Transaction {
|
|
|
81
108
|
public closeAndCommit(): void {
|
|
82
109
|
if (this.isOpen) {
|
|
83
110
|
if (this.transaction.changes.length > 0) {
|
|
84
|
-
const edit: Edit<ChangeInternal> = { id: newEditId(), changes: this.transaction.changes };
|
|
85
111
|
const result = this.transaction.close();
|
|
112
|
+
const edit: Edit<ChangeInternal> = { id: newEditId(), changes: result.changes };
|
|
86
113
|
if (this.tree.editsInternal instanceof CachingLogViewer) {
|
|
87
114
|
this.tree.editsInternal.setKnownEditingResult(edit, result);
|
|
88
115
|
}
|
|
89
116
|
this.tree.applyEditInternal(edit);
|
|
90
117
|
}
|
|
91
|
-
this.open = false;
|
|
92
118
|
}
|
|
93
119
|
}
|
|
94
120
|
}
|
|
@@ -34,12 +34,6 @@ import { RevisionView, TransactionView } from './RevisionView';
|
|
|
34
34
|
import { ReconciliationChange, ReconciliationPath } from './ReconciliationPath';
|
|
35
35
|
import { TreeViewNode } from './TreeView';
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* A function which can produce a Transaction from a RevisionView
|
|
39
|
-
* @public
|
|
40
|
-
*/
|
|
41
|
-
export type TransactionFactory = (view: RevisionView) => GenericTransaction;
|
|
42
|
-
|
|
43
37
|
/**
|
|
44
38
|
* Result of applying a transaction.
|
|
45
39
|
* @public
|
|
@@ -192,7 +186,7 @@ export class GenericTransaction {
|
|
|
192
186
|
private readonly policy: GenericTransactionPolicy;
|
|
193
187
|
protected readonly before: RevisionView;
|
|
194
188
|
private state: TransactionState;
|
|
195
|
-
private
|
|
189
|
+
private open = true;
|
|
196
190
|
|
|
197
191
|
/**
|
|
198
192
|
* Create and open an edit of the provided `TreeView`. After applying 0 or more changes, this editor should be closed via `close()`.
|
|
@@ -209,6 +203,11 @@ export class GenericTransaction {
|
|
|
209
203
|
};
|
|
210
204
|
}
|
|
211
205
|
|
|
206
|
+
/** Whether or not this transaction has been closed via `close()` */
|
|
207
|
+
public get isOpen(): boolean {
|
|
208
|
+
return this.open;
|
|
209
|
+
}
|
|
210
|
+
|
|
212
211
|
/**
|
|
213
212
|
* The most up-to-date `TreeView` for this edit. This is the state of the tree after all changes applied so far.
|
|
214
213
|
*/
|
|
@@ -239,8 +238,8 @@ export class GenericTransaction {
|
|
|
239
238
|
|
|
240
239
|
/** @returns the final `EditStatus` and `TreeView` after all changes are applied. */
|
|
241
240
|
public close(): EditingResult {
|
|
242
|
-
assert(this.
|
|
243
|
-
this.
|
|
241
|
+
assert(this.open, 'transaction has already been closed');
|
|
242
|
+
this.open = false;
|
|
244
243
|
if (this.state.status === EditStatus.Applied) {
|
|
245
244
|
const validation = this.policy.validateOnClose(this.state);
|
|
246
245
|
if (Result.isOk(validation)) {
|
|
@@ -337,7 +336,7 @@ export class GenericTransaction {
|
|
|
337
336
|
* @returns this
|
|
338
337
|
*/
|
|
339
338
|
public applyChange(change: ChangeInternal, path: ReconciliationPath = []): this {
|
|
340
|
-
assert(this.
|
|
339
|
+
assert(this.open, 'Editor must be open to apply changes.');
|
|
341
340
|
if (this.state.status !== EditStatus.Applied) {
|
|
342
341
|
fail('Cannot apply change to an edit unless all previous changes have applied');
|
|
343
342
|
}
|
package/src/TreeCompressor.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { Definition, DetachedSequenceId, InternedStringId, OpSpaceNodeId, T
|
|
|
7
7
|
import type { StringInterner } from './StringInterner';
|
|
8
8
|
import type { CompressedTraits, CompressedPlaceholderTree, PlaceholderTree, Payload } from './persisted-types';
|
|
9
9
|
import type { ContextualizedNodeIdNormalizer } from './NodeIdUtilities';
|
|
10
|
-
import { assert, fail } from './Common';
|
|
10
|
+
import { assert, fail, Mutable } from './Common';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Compresses a given {@link PlaceholderTree} into a more compact serializable format.
|
|
@@ -122,22 +122,21 @@ export class InterningTreeCompressor<TPlaceholder extends DetachedSequenceId | n
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
let compressedId: TId | undefined;
|
|
125
|
-
let compressedTraits:
|
|
125
|
+
let compressedTraits:
|
|
126
|
+
| [Payload, ...CompressedTraits<TId, TPlaceholder>]
|
|
127
|
+
| CompressedTraits<TId, TPlaceholder>
|
|
128
|
+
| undefined;
|
|
126
129
|
let payload: Payload | undefined;
|
|
127
130
|
const [maybeInternedDefinition, idOrPayloadTraits, payloadTraits] = node;
|
|
128
131
|
if (idOrPayloadTraits !== undefined) {
|
|
129
132
|
if (typeof idOrPayloadTraits === 'number') {
|
|
130
133
|
compressedId = idOrPayloadTraits;
|
|
131
134
|
if (payloadTraits !== undefined) {
|
|
132
|
-
|
|
135
|
+
compressedTraits = payloadTraits;
|
|
133
136
|
}
|
|
134
137
|
} else {
|
|
135
138
|
// TODO: This cast can be removed on typescript 4.6
|
|
136
|
-
|
|
137
|
-
idOrPayloadTraits as
|
|
138
|
-
| [Payload, ...CompressedTraits<TId, TPlaceholder>]
|
|
139
|
-
| CompressedTraits<TId, TPlaceholder>
|
|
140
|
-
));
|
|
139
|
+
compressedTraits = idOrPayloadTraits as typeof compressedTraits;
|
|
141
140
|
}
|
|
142
141
|
}
|
|
143
142
|
|
|
@@ -146,14 +145,30 @@ export class InterningTreeCompressor<TPlaceholder extends DetachedSequenceId | n
|
|
|
146
145
|
? maybeInternedDefinition
|
|
147
146
|
: // TODO: This cast can be removed on typescript 4.6
|
|
148
147
|
(interner.getString(maybeInternedDefinition as number) as Definition);
|
|
149
|
-
|
|
148
|
+
|
|
149
|
+
let identifier: TId;
|
|
150
|
+
if (compressedId !== undefined) {
|
|
151
|
+
identifier = compressedId;
|
|
152
|
+
} else {
|
|
153
|
+
const prevId = this.previousId ?? fail();
|
|
154
|
+
identifier = prevId < 0 ? ((prevId - 1) as TId) : (((prevId as number) + 1) as TId);
|
|
155
|
+
}
|
|
150
156
|
this.previousId = identifier;
|
|
151
157
|
|
|
152
158
|
const traits = {};
|
|
153
159
|
if (compressedTraits !== undefined) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
let offset: number;
|
|
161
|
+
if (compressedTraits.length % 2 === 1) {
|
|
162
|
+
offset = 1;
|
|
163
|
+
payload = compressedTraits[0];
|
|
164
|
+
} else {
|
|
165
|
+
offset = 0;
|
|
166
|
+
}
|
|
167
|
+
const traitsLength = compressedTraits.length - offset;
|
|
168
|
+
for (let i = 0; i < traitsLength; i += 2) {
|
|
169
|
+
const offsetIndex = i + offset;
|
|
170
|
+
const maybeCompressedLabel = compressedTraits[offsetIndex] as InternedStringId;
|
|
171
|
+
const compressedChildren = compressedTraits[offsetIndex + 1] as (
|
|
157
172
|
| TPlaceholder
|
|
158
173
|
| CompressedPlaceholderTree<TId, TPlaceholder>
|
|
159
174
|
)[];
|
|
@@ -170,42 +185,18 @@ export class InterningTreeCompressor<TPlaceholder extends DetachedSequenceId | n
|
|
|
170
185
|
}
|
|
171
186
|
}
|
|
172
187
|
|
|
173
|
-
const decompressedNode = {
|
|
188
|
+
const decompressedNode: Mutable<PlaceholderTree<TPlaceholder>> = {
|
|
174
189
|
identifier: idNormalizer.normalizeToSessionSpace(identifier),
|
|
175
190
|
definition,
|
|
176
191
|
traits,
|
|
177
|
-
...(payload !== undefined ? { payload } : {}),
|
|
178
192
|
};
|
|
179
193
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
private parseTraitsAndPayload<TId extends OpSpaceNodeId>(
|
|
184
|
-
traitsAndPayload: [Payload, ...CompressedTraits<TId, TPlaceholder>] | CompressedTraits<TId, TPlaceholder>
|
|
185
|
-
): {
|
|
186
|
-
compressedTraits: CompressedTraits<TId, TPlaceholder>;
|
|
187
|
-
payload?: Payload;
|
|
188
|
-
} {
|
|
189
|
-
if (traitsAndPayload.length % 2 === 1) {
|
|
190
|
-
return {
|
|
191
|
-
compressedTraits: traitsAndPayload.slice(1),
|
|
192
|
-
payload: traitsAndPayload[0],
|
|
193
|
-
};
|
|
194
|
+
if (payload !== undefined) {
|
|
195
|
+
decompressedNode.payload = payload;
|
|
194
196
|
}
|
|
195
197
|
|
|
196
|
-
return
|
|
197
|
-
compressedTraits: traitsAndPayload,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function getNextElidedId<TId extends OpSpaceNodeId>(id: TId): TId {
|
|
203
|
-
const numericId: number = id;
|
|
204
|
-
if (numericId < 0) {
|
|
205
|
-
return (numericId - 1) as TId;
|
|
198
|
+
return decompressedNode;
|
|
206
199
|
}
|
|
207
|
-
|
|
208
|
-
return (numericId + 1) as TId;
|
|
209
200
|
}
|
|
210
201
|
|
|
211
202
|
function canElideId<TId extends OpSpaceNodeId>(previousId: TId | undefined, id: TId): boolean {
|
package/src/TreeView.ts
CHANGED
|
@@ -291,8 +291,9 @@ export abstract class TreeView {
|
|
|
291
291
|
|
|
292
292
|
private *iterateNodeDescendants(node: TreeViewNode): IterableIterator<TreeViewNode> {
|
|
293
293
|
yield node;
|
|
294
|
-
for (const
|
|
295
|
-
|
|
294
|
+
for (const label of [...node.traits.keys()].sort()) {
|
|
295
|
+
const trait = node.traits.get(label);
|
|
296
|
+
for (const childId of trait ?? fail('Expected trait with label')) {
|
|
296
297
|
const child = this.getViewNode(childId);
|
|
297
298
|
yield* this.iterateNodeDescendants(child);
|
|
298
299
|
}
|