@fluidframework/tree 2.33.0-333010 → 2.33.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 +53 -0
- package/api-report/tree.alpha.api.md +169 -192
- package/api-report/tree.beta.api.md +135 -164
- package/api-report/tree.legacy.alpha.api.md +138 -167
- package/api-report/tree.legacy.public.api.md +135 -164
- package/api-report/tree.public.api.md +135 -164
- package/dist/alpha.d.ts +3 -11
- package/dist/beta.d.ts +2 -11
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/schema-stored/index.d.ts +1 -1
- package/dist/core/schema-stored/index.d.ts.map +1 -1
- package/dist/core/schema-stored/index.js +1 -2
- package/dist/core/schema-stored/index.js.map +1 -1
- package/dist/core/schema-stored/schema.d.ts +4 -11
- package/dist/core/schema-stored/schema.d.ts.map +1 -1
- package/dist/core/schema-stored/schema.js +7 -14
- package/dist/core/schema-stored/schema.js.map +1 -1
- package/dist/core/tree/anchorSet.d.ts.map +1 -1
- package/dist/core/tree/anchorSet.js +31 -24
- package/dist/core/tree/anchorSet.js.map +1 -1
- package/dist/core/tree/deltaUtil.d.ts +1 -4
- package/dist/core/tree/deltaUtil.d.ts.map +1 -1
- package/dist/core/tree/deltaUtil.js +1 -13
- package/dist/core/tree/deltaUtil.js.map +1 -1
- package/dist/core/tree/visitDelta.d.ts +6 -29
- package/dist/core/tree/visitDelta.d.ts.map +1 -1
- package/dist/core/tree/visitDelta.js +11 -50
- package/dist/core/tree/visitDelta.js.map +1 -1
- package/dist/core/tree/visitorUtils.d.ts +12 -9
- package/dist/core/tree/visitorUtils.d.ts.map +1 -1
- package/dist/core/tree/visitorUtils.js +19 -32
- package/dist/core/tree/visitorUtils.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/chunkedForest.js +6 -11
- package/dist/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/compressedEncode.d.ts +1 -1
- package/dist/feature-libraries/chunked-forest/codec/compressedEncode.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/compressedEncode.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/nodeShape.d.ts +41 -5
- package/dist/feature-libraries/chunked-forest/codec/nodeShape.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/nodeShape.js +43 -26
- package/dist/feature-libraries/chunked-forest/codec/nodeShape.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/schemaBasedEncode.js +1 -1
- package/dist/feature-libraries/chunked-forest/codec/schemaBasedEncode.js.map +1 -1
- package/dist/feature-libraries/indexing/anchorTreeIndex.d.ts.map +1 -1
- package/dist/feature-libraries/indexing/anchorTreeIndex.js +15 -22
- package/dist/feature-libraries/indexing/anchorTreeIndex.js.map +1 -1
- package/dist/feature-libraries/object-forest/objectForest.d.ts.map +1 -1
- package/dist/feature-libraries/object-forest/objectForest.js +1 -7
- package/dist/feature-libraries/object-forest/objectForest.js.map +1 -1
- package/dist/feature-libraries/schema-index/codec.d.ts.map +1 -1
- package/dist/feature-libraries/schema-index/codec.js +1 -1
- package/dist/feature-libraries/schema-index/codec.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/internalTypes.d.ts +1 -1
- package/dist/internalTypes.d.ts.map +1 -1
- package/dist/internalTypes.js.map +1 -1
- package/dist/jsonDomainSchema.d.ts +6 -6
- package/dist/jsonDomainSchema.d.ts.map +1 -1
- package/dist/jsonDomainSchema.js.map +1 -1
- package/dist/legacy.d.ts +2 -11
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/public.d.ts +2 -11
- package/dist/serializableDomainSchema.d.ts +5 -5
- package/dist/shared-tree/checkoutFlexTreeView.js +2 -2
- package/dist/shared-tree/checkoutFlexTreeView.js.map +1 -1
- package/dist/shared-tree/index.d.ts +3 -2
- package/dist/shared-tree/index.d.ts.map +1 -1
- package/dist/shared-tree/index.js +2 -2
- package/dist/shared-tree/index.js.map +1 -1
- package/dist/shared-tree/sharedTree.d.ts +4 -35
- package/dist/shared-tree/sharedTree.d.ts.map +1 -1
- package/dist/shared-tree/sharedTree.js +4 -54
- package/dist/shared-tree/sharedTree.js.map +1 -1
- package/dist/simple-tree/api/index.d.ts +2 -1
- package/dist/simple-tree/api/index.d.ts.map +1 -1
- package/dist/simple-tree/api/index.js +3 -1
- package/dist/simple-tree/api/index.js.map +1 -1
- package/dist/simple-tree/api/schemaFactory.d.ts +28 -28
- package/dist/simple-tree/api/schemaFactory.d.ts.map +1 -1
- package/dist/simple-tree/api/schemaFactory.js +11 -11
- package/dist/simple-tree/api/schemaFactory.js.map +1 -1
- package/dist/simple-tree/api/schemaFactoryAlpha.d.ts +6 -7
- package/dist/simple-tree/api/schemaFactoryAlpha.d.ts.map +1 -1
- package/dist/simple-tree/api/schemaFactoryAlpha.js.map +1 -1
- package/dist/simple-tree/api/schemaFactoryRecursive.d.ts +24 -5
- package/dist/simple-tree/api/schemaFactoryRecursive.d.ts.map +1 -1
- package/dist/simple-tree/api/schemaFactoryRecursive.js +14 -1
- package/dist/simple-tree/api/schemaFactoryRecursive.js.map +1 -1
- package/dist/simple-tree/api/testRecursiveDomain.d.ts +10 -10
- package/dist/simple-tree/api/tree.d.ts +1 -14
- package/dist/simple-tree/api/tree.d.ts.map +1 -1
- package/dist/simple-tree/api/tree.js.map +1 -1
- package/dist/simple-tree/api/typesUnsafe.d.ts +237 -220
- package/dist/simple-tree/api/typesUnsafe.d.ts.map +1 -1
- package/dist/simple-tree/api/typesUnsafe.js.map +1 -1
- package/dist/simple-tree/arrayNode.d.ts +2 -2
- package/dist/simple-tree/arrayNode.d.ts.map +1 -1
- package/dist/simple-tree/arrayNode.js.map +1 -1
- package/dist/simple-tree/core/types.d.ts +14 -4
- package/dist/simple-tree/core/types.d.ts.map +1 -1
- package/dist/simple-tree/core/types.js +14 -4
- package/dist/simple-tree/core/types.js.map +1 -1
- package/dist/simple-tree/index.d.ts +1 -1
- package/dist/simple-tree/index.d.ts.map +1 -1
- package/dist/simple-tree/index.js +3 -2
- package/dist/simple-tree/index.js.map +1 -1
- package/dist/simple-tree/objectNode.d.ts +9 -2
- package/dist/simple-tree/objectNode.d.ts.map +1 -1
- package/dist/simple-tree/objectNode.js.map +1 -1
- package/dist/simple-tree/schemaTypes.d.ts +10 -5
- package/dist/simple-tree/schemaTypes.d.ts.map +1 -1
- package/dist/simple-tree/schemaTypes.js +5 -2
- package/dist/simple-tree/schemaTypes.js.map +1 -1
- package/dist/tableSchema.d.ts +58 -20
- package/dist/tableSchema.d.ts.map +1 -1
- package/dist/tableSchema.js +40 -15
- package/dist/tableSchema.js.map +1 -1
- package/dist/treeFactory.d.ts +6 -12
- package/dist/treeFactory.d.ts.map +1 -1
- package/dist/treeFactory.js +56 -5
- package/dist/treeFactory.js.map +1 -1
- package/lib/alpha.d.ts +3 -11
- package/lib/beta.d.ts +2 -11
- package/lib/core/index.d.ts +1 -1
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +1 -1
- package/lib/core/index.js.map +1 -1
- package/lib/core/schema-stored/index.d.ts +1 -1
- package/lib/core/schema-stored/index.d.ts.map +1 -1
- package/lib/core/schema-stored/index.js +1 -1
- package/lib/core/schema-stored/index.js.map +1 -1
- package/lib/core/schema-stored/schema.d.ts +4 -11
- package/lib/core/schema-stored/schema.d.ts.map +1 -1
- package/lib/core/schema-stored/schema.js +6 -12
- package/lib/core/schema-stored/schema.js.map +1 -1
- package/lib/core/tree/anchorSet.d.ts.map +1 -1
- package/lib/core/tree/anchorSet.js +31 -24
- package/lib/core/tree/anchorSet.js.map +1 -1
- package/lib/core/tree/deltaUtil.d.ts +1 -4
- package/lib/core/tree/deltaUtil.d.ts.map +1 -1
- package/lib/core/tree/deltaUtil.js +0 -9
- package/lib/core/tree/deltaUtil.js.map +1 -1
- package/lib/core/tree/visitDelta.d.ts +6 -29
- package/lib/core/tree/visitDelta.d.ts.map +1 -1
- package/lib/core/tree/visitDelta.js +12 -51
- package/lib/core/tree/visitDelta.js.map +1 -1
- package/lib/core/tree/visitorUtils.d.ts +12 -9
- package/lib/core/tree/visitorUtils.d.ts.map +1 -1
- package/lib/core/tree/visitorUtils.js +19 -32
- package/lib/core/tree/visitorUtils.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/chunkedForest.js +6 -11
- package/lib/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/compressedEncode.d.ts +1 -1
- package/lib/feature-libraries/chunked-forest/codec/compressedEncode.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/compressedEncode.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/nodeShape.d.ts +41 -5
- package/lib/feature-libraries/chunked-forest/codec/nodeShape.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/nodeShape.js +43 -26
- package/lib/feature-libraries/chunked-forest/codec/nodeShape.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/schemaBasedEncode.js +1 -1
- package/lib/feature-libraries/chunked-forest/codec/schemaBasedEncode.js.map +1 -1
- package/lib/feature-libraries/indexing/anchorTreeIndex.d.ts.map +1 -1
- package/lib/feature-libraries/indexing/anchorTreeIndex.js +15 -22
- package/lib/feature-libraries/indexing/anchorTreeIndex.js.map +1 -1
- package/lib/feature-libraries/object-forest/objectForest.d.ts.map +1 -1
- package/lib/feature-libraries/object-forest/objectForest.js +1 -7
- package/lib/feature-libraries/object-forest/objectForest.js.map +1 -1
- package/lib/feature-libraries/schema-index/codec.d.ts.map +1 -1
- package/lib/feature-libraries/schema-index/codec.js +2 -2
- package/lib/feature-libraries/schema-index/codec.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/internalTypes.d.ts +1 -1
- package/lib/internalTypes.d.ts.map +1 -1
- package/lib/internalTypes.js.map +1 -1
- package/lib/jsonDomainSchema.d.ts +6 -6
- package/lib/jsonDomainSchema.d.ts.map +1 -1
- package/lib/jsonDomainSchema.js.map +1 -1
- package/lib/legacy.d.ts +2 -11
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/public.d.ts +2 -11
- package/lib/serializableDomainSchema.d.ts +5 -5
- package/lib/shared-tree/checkoutFlexTreeView.js +2 -2
- package/lib/shared-tree/checkoutFlexTreeView.js.map +1 -1
- package/lib/shared-tree/index.d.ts +3 -2
- package/lib/shared-tree/index.d.ts.map +1 -1
- package/lib/shared-tree/index.js +1 -1
- package/lib/shared-tree/index.js.map +1 -1
- package/lib/shared-tree/sharedTree.d.ts +4 -35
- package/lib/shared-tree/sharedTree.d.ts.map +1 -1
- package/lib/shared-tree/sharedTree.js +2 -51
- package/lib/shared-tree/sharedTree.js.map +1 -1
- package/lib/simple-tree/api/index.d.ts +2 -1
- package/lib/simple-tree/api/index.d.ts.map +1 -1
- package/lib/simple-tree/api/index.js +1 -0
- package/lib/simple-tree/api/index.js.map +1 -1
- package/lib/simple-tree/api/schemaFactory.d.ts +28 -28
- package/lib/simple-tree/api/schemaFactory.d.ts.map +1 -1
- package/lib/simple-tree/api/schemaFactory.js +11 -11
- package/lib/simple-tree/api/schemaFactory.js.map +1 -1
- package/lib/simple-tree/api/schemaFactoryAlpha.d.ts +6 -7
- package/lib/simple-tree/api/schemaFactoryAlpha.d.ts.map +1 -1
- package/lib/simple-tree/api/schemaFactoryAlpha.js.map +1 -1
- package/lib/simple-tree/api/schemaFactoryRecursive.d.ts +24 -5
- package/lib/simple-tree/api/schemaFactoryRecursive.d.ts.map +1 -1
- package/lib/simple-tree/api/schemaFactoryRecursive.js +12 -0
- package/lib/simple-tree/api/schemaFactoryRecursive.js.map +1 -1
- package/lib/simple-tree/api/testRecursiveDomain.d.ts +10 -10
- package/lib/simple-tree/api/tree.d.ts +1 -14
- package/lib/simple-tree/api/tree.d.ts.map +1 -1
- package/lib/simple-tree/api/tree.js.map +1 -1
- package/lib/simple-tree/api/typesUnsafe.d.ts +237 -220
- package/lib/simple-tree/api/typesUnsafe.d.ts.map +1 -1
- package/lib/simple-tree/api/typesUnsafe.js.map +1 -1
- package/lib/simple-tree/arrayNode.d.ts +2 -2
- package/lib/simple-tree/arrayNode.d.ts.map +1 -1
- package/lib/simple-tree/arrayNode.js.map +1 -1
- package/lib/simple-tree/core/types.d.ts +14 -4
- package/lib/simple-tree/core/types.d.ts.map +1 -1
- package/lib/simple-tree/core/types.js +14 -4
- package/lib/simple-tree/core/types.js.map +1 -1
- package/lib/simple-tree/index.d.ts +1 -1
- package/lib/simple-tree/index.d.ts.map +1 -1
- package/lib/simple-tree/index.js +1 -1
- package/lib/simple-tree/index.js.map +1 -1
- package/lib/simple-tree/objectNode.d.ts +9 -2
- package/lib/simple-tree/objectNode.d.ts.map +1 -1
- package/lib/simple-tree/objectNode.js.map +1 -1
- package/lib/simple-tree/schemaTypes.d.ts +10 -5
- package/lib/simple-tree/schemaTypes.d.ts.map +1 -1
- package/lib/simple-tree/schemaTypes.js +5 -2
- package/lib/simple-tree/schemaTypes.js.map +1 -1
- package/lib/tableSchema.d.ts +58 -20
- package/lib/tableSchema.d.ts.map +1 -1
- package/lib/tableSchema.js +40 -15
- package/lib/tableSchema.js.map +1 -1
- package/lib/treeFactory.d.ts +6 -12
- package/lib/treeFactory.d.ts.map +1 -1
- package/lib/treeFactory.js +55 -3
- package/lib/treeFactory.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/package.json +22 -23
- package/src/core/index.ts +0 -2
- package/src/core/schema-stored/index.ts +0 -2
- package/src/core/schema-stored/schema.ts +10 -29
- package/src/core/tree/anchorSet.ts +32 -24
- package/src/core/tree/deltaUtil.ts +1 -13
- package/src/core/tree/visitDelta.ts +24 -81
- package/src/core/tree/visitorUtils.ts +43 -53
- package/src/feature-libraries/chunked-forest/chunkedForest.ts +7 -22
- package/src/feature-libraries/chunked-forest/codec/compressedEncode.ts +1 -1
- package/src/feature-libraries/chunked-forest/codec/nodeShape.ts +45 -27
- package/src/feature-libraries/chunked-forest/codec/schemaBasedEncode.ts +1 -1
- package/src/feature-libraries/indexing/anchorTreeIndex.ts +17 -28
- package/src/feature-libraries/object-forest/objectForest.ts +1 -18
- package/src/feature-libraries/schema-index/codec.ts +1 -2
- package/src/index.ts +3 -11
- package/src/internalTypes.ts +0 -19
- package/src/jsonDomainSchema.ts +1 -3
- package/src/packageVersion.ts +1 -1
- package/src/shared-tree/checkoutFlexTreeView.ts +2 -2
- package/src/shared-tree/index.ts +8 -3
- package/src/shared-tree/sharedTree.ts +5 -133
- package/src/simple-tree/api/index.ts +2 -22
- package/src/simple-tree/api/schemaFactory.ts +40 -54
- package/src/simple-tree/api/schemaFactoryAlpha.ts +9 -14
- package/src/simple-tree/api/schemaFactoryRecursive.ts +25 -5
- package/src/simple-tree/api/tree.ts +1 -14
- package/src/simple-tree/api/typesUnsafe.ts +359 -341
- package/src/simple-tree/arrayNode.ts +2 -2
- package/src/simple-tree/core/types.ts +14 -4
- package/src/simple-tree/index.ts +2 -22
- package/src/simple-tree/objectNode.ts +9 -2
- package/src/simple-tree/schemaTypes.ts +11 -7
- package/src/tableSchema.ts +122 -54
- package/src/treeFactory.ts +151 -8
|
@@ -10,13 +10,7 @@ import type { FieldKey } from "../schema-stored/index.js";
|
|
|
10
10
|
|
|
11
11
|
import { mapCursorField, type ITreeCursorSynchronous } from "./cursor.js";
|
|
12
12
|
import type * as Delta from "./delta.js";
|
|
13
|
-
import {
|
|
14
|
-
areDetachedNodeIdsEqual,
|
|
15
|
-
isAttachMark,
|
|
16
|
-
isDetachMark,
|
|
17
|
-
isReplaceMark,
|
|
18
|
-
offsetDetachId,
|
|
19
|
-
} from "./deltaUtil.js";
|
|
13
|
+
import { areDetachedNodeIdsEqual, offsetDetachId } from "./deltaUtil.js";
|
|
20
14
|
import type { DetachedFieldIndex } from "./detachedFieldIndex.js";
|
|
21
15
|
import type { ForestRootId, Major, Minor } from "./detachedFieldIndexTypes.js";
|
|
22
16
|
import type { NodeIndex, PlaceIndex, Range } from "./pathTree.js";
|
|
@@ -32,24 +26,11 @@ import type { RevisionTag, TreeChunk } from "../index.js";
|
|
|
32
26
|
* 4. root destructions
|
|
33
27
|
*
|
|
34
28
|
* The core idea is that before content can be attached, it must first exist and be in a detached field.
|
|
35
|
-
* The detach pass is therefore responsible for making sure that all roots that needs to be attached during the
|
|
36
|
-
* attach pass are detached.
|
|
37
|
-
* In practice, this means the detach pass must:
|
|
38
|
-
* - Create all subtrees that need to be created
|
|
39
|
-
* - Detach all moved nodes
|
|
40
|
-
*
|
|
41
|
-
* In addition to that, the detach pass also detaches nodes that need removing, with the exception of nodes that get
|
|
42
|
-
* replaced. The reason for this exception is that we need to be able to communicate replaces as atomic operations.
|
|
43
|
-
* In order to do that, we need to wait until we are sure that the content to attach is available as a detached root.
|
|
44
|
-
* Replaces are therefore handled during the attach pass.
|
|
45
|
-
* Note that this could theoretically lead to a situation where, in the attach pass, one replace wants to attach
|
|
46
|
-
* a node that has yet to be detached by another replace. This does not occur in practice because we do not support
|
|
47
|
-
* editing operations that would lead to this situation.
|
|
48
29
|
*
|
|
49
30
|
* While the detach pass ensures that nodes to be attached are in a detached state, it does not guarantee that they
|
|
50
|
-
* reside in the correct
|
|
31
|
+
* reside in the correct detached field. That is the responsibility of the root transfers phase.
|
|
51
32
|
*
|
|
52
|
-
* The attach phase carries out attaches
|
|
33
|
+
* The attach phase carries out attaches.
|
|
53
34
|
*
|
|
54
35
|
* After the attach phase, roots destruction is carried out.
|
|
55
36
|
* This needs to happen last to allow modifications to detached roots to be applied before they are destroyed.
|
|
@@ -57,9 +38,6 @@ import type { RevisionTag, TreeChunk } from "../index.js";
|
|
|
57
38
|
* The details of the delta visit algorithm can impact how/when events are emitted by the objects that own the visitors.
|
|
58
39
|
* For example, as of 2024-03-27, the subtreeChanged event of an AnchorNode is emitted when exiting a node during a
|
|
59
40
|
* delta visit, and thus the two-pass nature of the algorithm means the event fires twice for any given change.
|
|
60
|
-
* This two-pass nature also means that the event may fire at a time where no change is visible in the tree. E.g.,
|
|
61
|
-
* if a node is being replaced, when the event fires during the detach pass no change in the tree has happened so the
|
|
62
|
-
* listener won't see any; then when it fires during the attach pass, the change will be visible in the event listener.
|
|
63
41
|
*/
|
|
64
42
|
|
|
65
43
|
/**
|
|
@@ -233,7 +211,7 @@ function transferRoots(
|
|
|
233
211
|
const oldField = detachedFieldIndex.toFieldKey(oldRootId);
|
|
234
212
|
const newField = detachedFieldIndex.toFieldKey(newRootId);
|
|
235
213
|
visitor.enterField(oldField);
|
|
236
|
-
visitor.detach({ start: 0, end: 1 }, newField, newId);
|
|
214
|
+
visitor.detach({ start: 0, end: 1 }, newField, newId, false);
|
|
237
215
|
visitor.exitField(oldField);
|
|
238
216
|
detachedFieldIndex.deleteEntry(oldId);
|
|
239
217
|
}
|
|
@@ -285,22 +263,15 @@ export interface DeltaVisitor {
|
|
|
285
263
|
* @param destination - The key for a new detached field.
|
|
286
264
|
* A field with this key must not already exist.
|
|
287
265
|
* @param id - The ID assigned to the first detached node as a result of the detach. The other nodes in the detached range are assigned subsequent IDs.
|
|
266
|
+
* @param isReplaced - Whether the detached content will be replaced by a later attach.
|
|
267
|
+
* This is not guaranteed to be true in all cases where it could be true,
|
|
268
|
+
* but it is guaranteed to be true in all cases where a later attach is needed to keep the data compliant with the schema.
|
|
288
269
|
*/
|
|
289
|
-
detach(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
* @param newContentSource - The detached field to transfer the new nodes from.
|
|
295
|
-
* @param range - The bounds of the range of nodes to replace.
|
|
296
|
-
* @param oldContentDestination - The key for a new detached field to transfer the old nodes to.
|
|
297
|
-
* @param oldContentId - The ID assigned to the first replaced node as a result of the replace. The other nodes in the replaced range are assigned subsequent IDs.
|
|
298
|
-
*/
|
|
299
|
-
replace(
|
|
300
|
-
newContentSource: FieldKey,
|
|
301
|
-
range: Range,
|
|
302
|
-
oldContentDestination: FieldKey,
|
|
303
|
-
oldContentId: Delta.DetachedNodeId,
|
|
270
|
+
detach(
|
|
271
|
+
source: Range,
|
|
272
|
+
destination: FieldKey,
|
|
273
|
+
id: Delta.DetachedNodeId,
|
|
274
|
+
isReplaced: boolean,
|
|
304
275
|
): void;
|
|
305
276
|
|
|
306
277
|
/**
|
|
@@ -417,13 +388,8 @@ function visitNode(
|
|
|
417
388
|
|
|
418
389
|
/**
|
|
419
390
|
* Performs the following:
|
|
420
|
-
* - Performs all root creations
|
|
421
|
-
* - Collects all roots that may need a detach pass
|
|
422
391
|
* - Collects all roots that may need an attach pass
|
|
423
|
-
* -
|
|
424
|
-
* - Collects all destructions
|
|
425
|
-
* - Executes detaches (bottom-up) provided they are not part of a replace
|
|
426
|
-
* (because we want to wait until we are sure content to attach is available as a root)
|
|
392
|
+
* - Executes detaches (bottom-up)
|
|
427
393
|
*/
|
|
428
394
|
function detachPass(
|
|
429
395
|
fieldChanges: Delta.FieldChanges,
|
|
@@ -439,18 +405,18 @@ function detachPass(
|
|
|
439
405
|
);
|
|
440
406
|
visitNode(index, mark.fields, visitor, config);
|
|
441
407
|
}
|
|
442
|
-
if (
|
|
408
|
+
if (mark.detach !== undefined) {
|
|
443
409
|
for (let i = 0; i < mark.count; i += 1) {
|
|
444
|
-
|
|
445
|
-
const id = offsetDetachId(mark.detach!, i);
|
|
410
|
+
const id = offsetDetachId(mark.detach, i);
|
|
446
411
|
const root = config.detachedFieldIndex.createEntry(id, config.latestRevision);
|
|
447
412
|
if (mark.fields !== undefined) {
|
|
448
413
|
config.attachPassRoots.set(root, mark.fields);
|
|
449
414
|
}
|
|
450
415
|
const field = config.detachedFieldIndex.toFieldKey(root);
|
|
451
|
-
visitor.detach({ start: index, end: index + 1 }, field, id);
|
|
416
|
+
visitor.detach({ start: index, end: index + 1 }, field, id, mark.attach !== undefined);
|
|
452
417
|
}
|
|
453
|
-
}
|
|
418
|
+
}
|
|
419
|
+
if (mark.detach === undefined && mark.attach === undefined) {
|
|
454
420
|
index += mark.count;
|
|
455
421
|
}
|
|
456
422
|
}
|
|
@@ -534,8 +500,6 @@ function collectDestroys(
|
|
|
534
500
|
/**
|
|
535
501
|
* Preforms the following:
|
|
536
502
|
* - Executes attaches (top-down) applying nested changes on the attached nodes
|
|
537
|
-
* - Executes replaces (top-down) applying nested changes on the attached nodes
|
|
538
|
-
* - Collects detached roots (from replaces) that need an attach pass
|
|
539
503
|
*/
|
|
540
504
|
function attachPass(
|
|
541
505
|
fieldChanges: Delta.FieldChanges,
|
|
@@ -544,10 +508,9 @@ function attachPass(
|
|
|
544
508
|
): void {
|
|
545
509
|
let index = 0;
|
|
546
510
|
for (const mark of fieldChanges) {
|
|
547
|
-
if (
|
|
511
|
+
if (mark.attach !== undefined) {
|
|
548
512
|
for (let i = 0; i < mark.count; i += 1) {
|
|
549
|
-
|
|
550
|
-
const offsetAttachId = offsetDetachId(mark.attach!, i);
|
|
513
|
+
const offsetAttachId = offsetDetachId(mark.attach, i);
|
|
551
514
|
let sourceRoot = config.detachedFieldIndex.tryGetEntry(offsetAttachId);
|
|
552
515
|
if (sourceRoot === undefined) {
|
|
553
516
|
const tree = tryGetFromNestedMap(
|
|
@@ -567,28 +530,7 @@ function attachPass(
|
|
|
567
530
|
}
|
|
568
531
|
const sourceField = config.detachedFieldIndex.toFieldKey(sourceRoot);
|
|
569
532
|
const offsetIndex = index + i;
|
|
570
|
-
|
|
571
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
572
|
-
const destinationId = offsetDetachId(mark.detach!, i);
|
|
573
|
-
const rootDestination = config.detachedFieldIndex.createEntry(
|
|
574
|
-
destinationId,
|
|
575
|
-
config.latestRevision,
|
|
576
|
-
);
|
|
577
|
-
const destinationField = config.detachedFieldIndex.toFieldKey(rootDestination);
|
|
578
|
-
visitor.replace(
|
|
579
|
-
sourceField,
|
|
580
|
-
{ start: offsetIndex, end: offsetIndex + 1 },
|
|
581
|
-
destinationField,
|
|
582
|
-
destinationId,
|
|
583
|
-
);
|
|
584
|
-
// We may need to do a second pass on the detached nodes
|
|
585
|
-
if (mark.fields !== undefined) {
|
|
586
|
-
config.attachPassRoots.set(rootDestination, mark.fields);
|
|
587
|
-
}
|
|
588
|
-
} else {
|
|
589
|
-
// This a simple attach
|
|
590
|
-
visitor.attach(sourceField, 1, offsetIndex);
|
|
591
|
-
}
|
|
533
|
+
visitor.attach(sourceField, 1, offsetIndex);
|
|
592
534
|
config.detachedFieldIndex.deleteEntry(offsetAttachId);
|
|
593
535
|
const fields = config.attachPassRoots.get(sourceRoot);
|
|
594
536
|
if (fields !== undefined) {
|
|
@@ -596,10 +538,11 @@ function attachPass(
|
|
|
596
538
|
visitNode(offsetIndex, fields, visitor, config);
|
|
597
539
|
}
|
|
598
540
|
}
|
|
599
|
-
}
|
|
541
|
+
}
|
|
542
|
+
if (mark.detach === undefined && mark.fields !== undefined) {
|
|
600
543
|
visitNode(index, mark.fields, visitor, config);
|
|
601
544
|
}
|
|
602
|
-
if (
|
|
545
|
+
if (mark.detach === undefined || mark.attach !== undefined) {
|
|
603
546
|
index += mark.count;
|
|
604
547
|
}
|
|
605
548
|
}
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { assert } from "@fluidframework/core-utils/internal";
|
|
7
|
-
|
|
8
6
|
import type { ICodecOptions } from "../../codec/index.js";
|
|
9
7
|
import { type IdAllocator, idAllocatorFromMaxId } from "../../util/index.js";
|
|
10
8
|
import type { RevisionTag, RevisionTagCodec } from "../rebase/index.js";
|
|
@@ -51,41 +49,46 @@ export function announceDelta(
|
|
|
51
49
|
detachedFieldIndex: DetachedFieldIndex,
|
|
52
50
|
): void {
|
|
53
51
|
const visitor = deltaProcessor.acquireVisitor();
|
|
54
|
-
visitDelta(delta, combineVisitors([visitor]
|
|
52
|
+
visitDelta(delta, combineVisitors([visitor]), detachedFieldIndex, latestRevision);
|
|
55
53
|
visitor.free();
|
|
56
54
|
}
|
|
57
55
|
|
|
56
|
+
export interface CombinedVisitor extends DeltaVisitor {
|
|
57
|
+
readonly type: "Combined";
|
|
58
|
+
|
|
59
|
+
readonly visitors: readonly CombinableVisitor[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type CombinableVisitor =
|
|
63
|
+
| (DeltaVisitor & { type?: never })
|
|
64
|
+
| AnnouncedVisitor
|
|
65
|
+
| CombinedVisitor;
|
|
66
|
+
|
|
58
67
|
/**
|
|
68
|
+
* Combines multiple visitors into a single visitor.
|
|
59
69
|
* @param visitors - The returned visitor invokes the corresponding events for all these visitors, in order.
|
|
60
|
-
* @param announcedVisitors - Subset of `visitors` to also call {@link AnnouncedVisitor} methods on.
|
|
61
|
-
* This must be a subset of `visitors`: if not the visitor will not have its path correctly set when the events are triggered.
|
|
62
|
-
* When `visitors` are making changes to data, `announcedVisitors` can be used to get extra events before or after all the changes from all the visitors have been made.
|
|
63
|
-
* This can, for example, enable visitors to have access to the tree in these extra events despite multiple separate visitors updating different tree related data-structures.
|
|
64
70
|
* @returns a DeltaVisitor combining all `visitors`.
|
|
65
71
|
*/
|
|
66
|
-
export function combineVisitors(
|
|
67
|
-
visitors:
|
|
68
|
-
announcedVisitors
|
|
69
|
-
):
|
|
70
|
-
|
|
71
|
-
const set = new Set(visitors);
|
|
72
|
-
for (const item of announcedVisitors) {
|
|
73
|
-
assert(set.has(item), 0x8c8 /* AnnouncedVisitor would not get traversed */);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
72
|
+
export function combineVisitors(visitors: readonly CombinableVisitor[]): CombinedVisitor {
|
|
73
|
+
const allVisitors = visitors.flatMap((v) => (v.type === "Combined" ? v.visitors : [v]));
|
|
74
|
+
const announcedVisitors = allVisitors.filter(
|
|
75
|
+
(v): v is AnnouncedVisitor => v.type === "Announced",
|
|
76
|
+
);
|
|
76
77
|
return {
|
|
78
|
+
type: "Combined",
|
|
79
|
+
visitors: allVisitors,
|
|
77
80
|
free: () => visitors.forEach((v) => v.free()),
|
|
78
81
|
create: (...args) => {
|
|
79
|
-
|
|
82
|
+
allVisitors.forEach((v) => v.create(...args));
|
|
80
83
|
announcedVisitors.forEach((v) => v.afterCreate(...args));
|
|
81
84
|
},
|
|
82
85
|
destroy: (...args) => {
|
|
83
86
|
announcedVisitors.forEach((v) => v.beforeDestroy(...args));
|
|
84
|
-
|
|
87
|
+
allVisitors.forEach((v) => v.destroy(...args));
|
|
85
88
|
},
|
|
86
89
|
attach: (source: FieldKey, count: number, destination: PlaceIndex) => {
|
|
87
90
|
announcedVisitors.forEach((v) => v.beforeAttach(source, count, destination));
|
|
88
|
-
|
|
91
|
+
allVisitors.forEach((v) => v.attach(source, count, destination));
|
|
89
92
|
announcedVisitors.forEach((v) =>
|
|
90
93
|
v.afterAttach(source, {
|
|
91
94
|
start: destination,
|
|
@@ -93,33 +96,22 @@ export function combineVisitors(
|
|
|
93
96
|
}),
|
|
94
97
|
);
|
|
95
98
|
},
|
|
96
|
-
detach: (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
);
|
|
102
|
-
},
|
|
103
|
-
replace: (
|
|
104
|
-
newContent: FieldKey,
|
|
105
|
-
oldContent: Range,
|
|
106
|
-
oldContentDestination: FieldKey,
|
|
107
|
-
oldContentId: DetachedNodeId,
|
|
99
|
+
detach: (
|
|
100
|
+
source: Range,
|
|
101
|
+
destination: FieldKey,
|
|
102
|
+
id: DetachedNodeId,
|
|
103
|
+
isReplaced: boolean,
|
|
108
104
|
) => {
|
|
105
|
+
announcedVisitors.forEach((v) => v.beforeDetach(source, destination, isReplaced));
|
|
106
|
+
allVisitors.forEach((v) => v.detach(source, destination, id, isReplaced));
|
|
109
107
|
announcedVisitors.forEach((v) =>
|
|
110
|
-
v.
|
|
111
|
-
);
|
|
112
|
-
visitors.forEach((v) =>
|
|
113
|
-
v.replace(newContent, oldContent, oldContentDestination, oldContentId),
|
|
114
|
-
);
|
|
115
|
-
announcedVisitors.forEach((v) =>
|
|
116
|
-
v.afterReplace(newContent, oldContent, oldContentDestination),
|
|
108
|
+
v.afterDetach(source.start, source.end - source.start, destination, isReplaced),
|
|
117
109
|
);
|
|
118
110
|
},
|
|
119
|
-
enterNode: (...args) =>
|
|
120
|
-
exitNode: (...args) =>
|
|
121
|
-
enterField: (...args) =>
|
|
122
|
-
exitField: (...args) =>
|
|
111
|
+
enterNode: (...args) => allVisitors.forEach((v) => v.enterNode(...args)),
|
|
112
|
+
exitNode: (...args) => allVisitors.forEach((v) => v.exitNode(...args)),
|
|
113
|
+
enterField: (...args) => allVisitors.forEach((v) => v.enterField(...args)),
|
|
114
|
+
exitField: (...args) => allVisitors.forEach((v) => v.exitField(...args)),
|
|
123
115
|
};
|
|
124
116
|
}
|
|
125
117
|
|
|
@@ -128,6 +120,7 @@ export function combineVisitors(
|
|
|
128
120
|
* Must be freed after use.
|
|
129
121
|
*/
|
|
130
122
|
export interface AnnouncedVisitor extends DeltaVisitor {
|
|
123
|
+
readonly type: "Announced";
|
|
131
124
|
/**
|
|
132
125
|
* A hook that is called after all nodes have been created.
|
|
133
126
|
*/
|
|
@@ -135,14 +128,13 @@ export interface AnnouncedVisitor extends DeltaVisitor {
|
|
|
135
128
|
beforeDestroy(field: FieldKey, count: number): void;
|
|
136
129
|
beforeAttach(source: FieldKey, count: number, destination: PlaceIndex): void;
|
|
137
130
|
afterAttach(source: FieldKey, destination: Range): void;
|
|
138
|
-
beforeDetach(source: Range, destination: FieldKey): void;
|
|
139
|
-
afterDetach(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
131
|
+
beforeDetach(source: Range, destination: FieldKey, isReplaced: boolean): void;
|
|
132
|
+
afterDetach(
|
|
133
|
+
source: PlaceIndex,
|
|
134
|
+
count: number,
|
|
135
|
+
destination: FieldKey,
|
|
136
|
+
isReplaced: boolean,
|
|
144
137
|
): void;
|
|
145
|
-
afterReplace(newContentSource: FieldKey, newContent: Range, oldContent: FieldKey): void;
|
|
146
138
|
}
|
|
147
139
|
|
|
148
140
|
/**
|
|
@@ -154,6 +146,7 @@ export function createAnnouncedVisitor(
|
|
|
154
146
|
): AnnouncedVisitor {
|
|
155
147
|
const noOp = (): void => {};
|
|
156
148
|
return {
|
|
149
|
+
type: "Announced",
|
|
157
150
|
free: visitorFunctions.free ?? noOp,
|
|
158
151
|
create: visitorFunctions.create ?? noOp,
|
|
159
152
|
afterCreate: visitorFunctions.afterCreate ?? noOp,
|
|
@@ -165,9 +158,6 @@ export function createAnnouncedVisitor(
|
|
|
165
158
|
beforeDetach: visitorFunctions.beforeDetach ?? noOp,
|
|
166
159
|
detach: visitorFunctions.detach ?? noOp,
|
|
167
160
|
afterDetach: visitorFunctions.afterDetach ?? noOp,
|
|
168
|
-
beforeReplace: visitorFunctions.beforeReplace ?? noOp,
|
|
169
|
-
replace: visitorFunctions.replace ?? noOp,
|
|
170
|
-
afterReplace: visitorFunctions.afterReplace ?? noOp,
|
|
171
161
|
enterNode: visitorFunctions.enterNode ?? noOp,
|
|
172
162
|
exitNode: visitorFunctions.exitNode ?? noOp,
|
|
173
163
|
enterField: visitorFunctions.enterField ?? noOp,
|
|
@@ -183,6 +183,12 @@ export class ChunkedForest implements IEditableForest {
|
|
|
183
183
|
* If not specified, the detached range is destroyed.
|
|
184
184
|
*/
|
|
185
185
|
detachEdit(source: Range, destination: FieldKey | undefined): void {
|
|
186
|
+
// TODO: optimize this to perform in-place replace in uniform chunks when attach edits bring the chunk back to its original shape.
|
|
187
|
+
// This should result in 3 cases:
|
|
188
|
+
// 1. In-place update of uniform chunk. No allocations, no ref count changes, no new TreeChunks.
|
|
189
|
+
// 2. Uniform chunk is shared: copy it (and parent path as needed), and update the copy.
|
|
190
|
+
// 3. Fallback to detach then attach (Which will copy parents and convert to basic chunks as needed).
|
|
191
|
+
|
|
186
192
|
this.forest.#events.emit("beforeChange");
|
|
187
193
|
const parent = this.getParent();
|
|
188
194
|
const sourceField = parent.mutableChunk.fields.get(parent.key) ?? [];
|
|
@@ -209,24 +215,6 @@ export class ChunkedForest implements IEditableForest {
|
|
|
209
215
|
parent.mutableChunk.fields.delete(parent.key);
|
|
210
216
|
}
|
|
211
217
|
},
|
|
212
|
-
replace(
|
|
213
|
-
newContentSource: FieldKey,
|
|
214
|
-
range: Range,
|
|
215
|
-
oldContentDestination: FieldKey,
|
|
216
|
-
oldContentId: DeltaDetachedNodeId,
|
|
217
|
-
): void {
|
|
218
|
-
assert(
|
|
219
|
-
newContentSource !== oldContentDestination,
|
|
220
|
-
0x7b0 /* Replace detached source field and detached destination field must be different */,
|
|
221
|
-
);
|
|
222
|
-
// TODO: optimize this to: perform in-place replace in uniform chunks when possible.
|
|
223
|
-
// This should result in 3 cases:
|
|
224
|
-
// 1. In-place update of uniform chunk. No allocations, no ref count changes, no new TreeChunks.
|
|
225
|
-
// 2. Uniform chunk is shared: copy it (and parent path as needed), and update the copy.
|
|
226
|
-
// 3. Fallback to detach then attach (Which will copy parents and convert to basic chunks as needed).
|
|
227
|
-
this.detachEdit(range, oldContentDestination);
|
|
228
|
-
this.attachEdit(newContentSource, range.end - range.start, range.start);
|
|
229
|
-
},
|
|
230
218
|
enterNode(index: number): void {
|
|
231
219
|
assert(this.mutableChunk === undefined, 0x535 /* should be in field */);
|
|
232
220
|
const parent = this.getParent();
|
|
@@ -289,10 +277,7 @@ export class ChunkedForest implements IEditableForest {
|
|
|
289
277
|
|
|
290
278
|
const announcedVisitors: AnnouncedVisitor[] = [];
|
|
291
279
|
this.deltaVisitors.forEach((getVisitor) => announcedVisitors.push(getVisitor()));
|
|
292
|
-
const combinedVisitor = combineVisitors(
|
|
293
|
-
[forestVisitor, ...announcedVisitors],
|
|
294
|
-
announcedVisitors,
|
|
295
|
-
);
|
|
280
|
+
const combinedVisitor = combineVisitors([forestVisitor, ...announcedVisitors]);
|
|
296
281
|
this.activeVisitor = combinedVisitor;
|
|
297
282
|
return combinedVisitor;
|
|
298
283
|
}
|
|
@@ -27,17 +27,35 @@ import type { EncodedChunkShape, EncodedFieldShape, EncodedValueShape } from "./
|
|
|
27
27
|
import { isStableId } from "@fluidframework/id-compressor/internal";
|
|
28
28
|
|
|
29
29
|
export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Set of keys for fields that are encoded using {@link NodeShape.specializedFieldEncoders}.
|
|
32
|
+
* TODO: Ensure uniform chunks, encoding and identifier generation sort fields the same.
|
|
33
|
+
*/
|
|
34
|
+
private readonly specializedFieldKeys: Set<FieldKey>;
|
|
32
35
|
|
|
33
36
|
public constructor(
|
|
34
37
|
public readonly type: undefined | TreeNodeSchemaIdentifier,
|
|
35
38
|
public readonly value: EncodedValueShape,
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Encoders for a specific set of fields, by key, in the order they will be encoded.
|
|
41
|
+
* These are fields for which specialized encoding is provided as an optimization.
|
|
42
|
+
* Using these for a given field instead of falling back to {@link NodeShape.specializedFieldEncoders} is often more efficient:
|
|
43
|
+
* this avoids the need to explicitly include the key and shape in the encoded data for each node instance.
|
|
44
|
+
* Instead, this information is here, and thus is encoded only once as part of the node shape.
|
|
45
|
+
* These encoders will be used, even if the field they apply to is empty (which can add overhead for fields which are usually empty).
|
|
46
|
+
*
|
|
47
|
+
* Any fields not included here will be encoded using {@link NodeShape.otherFieldsEncoder}.
|
|
48
|
+
* If {@link NodeShape.otherFieldsEncoder} is undefined, then this must handle all non-empty fields.
|
|
49
|
+
*/
|
|
50
|
+
public readonly specializedFieldEncoders: readonly KeyedFieldEncoder[],
|
|
51
|
+
/**
|
|
52
|
+
* Encoder for all other fields that are not in {@link NodeShape.specializedFieldEncoders}. These fields must
|
|
53
|
+
* be encoded after the specialized fields.
|
|
54
|
+
*/
|
|
55
|
+
public readonly otherFieldsEncoder: undefined | FieldEncoder,
|
|
38
56
|
) {
|
|
39
57
|
super();
|
|
40
|
-
this.
|
|
58
|
+
this.specializedFieldKeys = new Set(this.specializedFieldEncoders.map((f) => f.key));
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
private getValueToEncode(cursor: ITreeCursorSynchronous, cache: EncoderCache): Value {
|
|
@@ -64,28 +82,28 @@ export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
|
|
|
64
82
|
assert(cursor.type === this.type, 0x741 /* type must match shape */);
|
|
65
83
|
}
|
|
66
84
|
encodeValue(this.getValueToEncode(cursor, cache), this.value, outputBuffer);
|
|
67
|
-
for (const
|
|
68
|
-
cursor.enterField(brand(
|
|
69
|
-
|
|
85
|
+
for (const fieldEncoder of this.specializedFieldEncoders) {
|
|
86
|
+
cursor.enterField(brand(fieldEncoder.key));
|
|
87
|
+
fieldEncoder.encoder.encodeField(cursor, cache, outputBuffer);
|
|
70
88
|
cursor.exitField();
|
|
71
89
|
}
|
|
72
90
|
|
|
73
|
-
const
|
|
91
|
+
const otherFieldsBuffer: BufferFormat<EncodedChunkShape> = [];
|
|
74
92
|
|
|
75
93
|
forEachField(cursor, () => {
|
|
76
94
|
const key = cursor.getFieldKey();
|
|
77
|
-
if (!this.
|
|
95
|
+
if (!this.specializedFieldKeys.has(key)) {
|
|
78
96
|
assert(
|
|
79
|
-
this.
|
|
97
|
+
this.otherFieldsEncoder !== undefined,
|
|
80
98
|
0x742 /* had extra local fields when shape does not support them */,
|
|
81
99
|
);
|
|
82
|
-
|
|
83
|
-
this.
|
|
100
|
+
otherFieldsBuffer.push(new IdentifierToken(key));
|
|
101
|
+
this.otherFieldsEncoder.encodeField(cursor, cache, otherFieldsBuffer);
|
|
84
102
|
}
|
|
85
103
|
});
|
|
86
104
|
|
|
87
|
-
if (this.
|
|
88
|
-
outputBuffer.push(
|
|
105
|
+
if (this.otherFieldsEncoder !== undefined) {
|
|
106
|
+
outputBuffer.push(otherFieldsBuffer);
|
|
89
107
|
}
|
|
90
108
|
}
|
|
91
109
|
|
|
@@ -97,8 +115,8 @@ export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
|
|
|
97
115
|
c: {
|
|
98
116
|
type: encodeOptionalIdentifier(this.type, identifiers),
|
|
99
117
|
value: this.value,
|
|
100
|
-
fields: encodeFieldShapes(this.
|
|
101
|
-
extraFields: encodeOptionalFieldShape(this.
|
|
118
|
+
fields: encodeFieldShapes(this.specializedFieldEncoders, identifiers, shapes),
|
|
119
|
+
extraFields: encodeOptionalFieldShape(this.otherFieldsEncoder, shapes),
|
|
102
120
|
},
|
|
103
121
|
};
|
|
104
122
|
}
|
|
@@ -111,13 +129,13 @@ export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
|
|
|
111
129
|
identifiers.add(this.type);
|
|
112
130
|
}
|
|
113
131
|
|
|
114
|
-
for (const
|
|
115
|
-
identifiers.add(
|
|
116
|
-
shapes(
|
|
132
|
+
for (const fieldEncoder of this.specializedFieldEncoders) {
|
|
133
|
+
identifiers.add(fieldEncoder.key);
|
|
134
|
+
shapes(fieldEncoder.encoder.shape);
|
|
117
135
|
}
|
|
118
136
|
|
|
119
|
-
if (this.
|
|
120
|
-
shapes(this.
|
|
137
|
+
if (this.otherFieldsEncoder !== undefined) {
|
|
138
|
+
shapes(this.otherFieldsEncoder.shape);
|
|
121
139
|
}
|
|
122
140
|
}
|
|
123
141
|
|
|
@@ -127,18 +145,18 @@ export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
|
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
export function encodeFieldShapes(
|
|
130
|
-
|
|
148
|
+
fieldEncoders: readonly KeyedFieldEncoder[],
|
|
131
149
|
identifiers: DeduplicationTable<string>,
|
|
132
150
|
shapes: DeduplicationTable<Shape<EncodedChunkShape>>,
|
|
133
151
|
): EncodedFieldShape[] | undefined {
|
|
134
|
-
if (
|
|
152
|
+
if (fieldEncoders.length === 0) {
|
|
135
153
|
return undefined;
|
|
136
154
|
}
|
|
137
|
-
return
|
|
155
|
+
return fieldEncoders.map((fieldEncoder) => [
|
|
138
156
|
// key
|
|
139
|
-
encodeIdentifier(
|
|
157
|
+
encodeIdentifier(fieldEncoder.key, identifiers),
|
|
140
158
|
// shape
|
|
141
|
-
shapes.valueToIndex.get(
|
|
159
|
+
shapes.valueToIndex.get(fieldEncoder.encoder.shape) ?? fail(0xb50 /* missing shape */),
|
|
142
160
|
]);
|
|
143
161
|
}
|
|
144
162
|
|
|
@@ -122,7 +122,7 @@ export function treeShaper(
|
|
|
122
122
|
|
|
123
123
|
const objectNodeFields: KeyedFieldEncoder[] = [];
|
|
124
124
|
for (const [key, field] of schema.objectNodeFields ?? []) {
|
|
125
|
-
objectNodeFields.push({ key,
|
|
125
|
+
objectNodeFields.push({ key, encoder: fieldHandler.shapeFromField(field) });
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
const shape = new NodeShape(schemaName, false, objectNodeFields, undefined);
|
|
@@ -139,27 +139,14 @@ export class AnchorTreeIndex<TKey extends TreeIndexKey, TValue>
|
|
|
139
139
|
assert(parent !== undefined, 0xa99 /* must have a parent */);
|
|
140
140
|
this.reIndexSpine(parent);
|
|
141
141
|
},
|
|
142
|
-
afterDetach: () => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const cursor = this.forest.allocateCursor();
|
|
150
|
-
this.forest.moveCursorToPath(parent, cursor);
|
|
151
|
-
assert(
|
|
152
|
-
cursor.mode === CursorLocationType.Nodes,
|
|
153
|
-
0xa8c /* replace should happen in a node */,
|
|
154
|
-
);
|
|
155
|
-
cursor.exitNode();
|
|
156
|
-
this.indexField(cursor);
|
|
157
|
-
if (!this.isShallowIndex) {
|
|
158
|
-
// we must also re-index the spine if the key finders allow for any value under a subtree to be the key
|
|
159
|
-
// this means that a replace can cause the key for any node up its spine to be changed
|
|
160
|
-
this.indexSpine(cursor);
|
|
142
|
+
afterDetach: (_source, _count_, _destination, isReplaced) => {
|
|
143
|
+
if (isReplaced) {
|
|
144
|
+
// If the node will be replaced, we defer re-indexing until the corresponding attach event.
|
|
145
|
+
// This has performance benefits but is also required to avoid experiencing the error case where the field that is used as the indexing key is empty.
|
|
146
|
+
} else {
|
|
147
|
+
assert(parent !== undefined, 0xa9a /* must have a parent */);
|
|
148
|
+
this.reIndexSpine(parent);
|
|
161
149
|
}
|
|
162
|
-
cursor.clear();
|
|
163
150
|
},
|
|
164
151
|
// the methods below are used to keep track of the path that has been traversed by the visitor
|
|
165
152
|
// this is required so that cursors can be moved to the correct location when index updates are required
|
|
@@ -315,17 +302,19 @@ export class AnchorTreeIndex<TKey extends TreeIndexKey, TValue>
|
|
|
315
302
|
* Checks if the spine needs to be re-indexed and if so, re-indexes it starting from the given path.
|
|
316
303
|
*/
|
|
317
304
|
private reIndexSpine(path: UpPath): void {
|
|
305
|
+
const cursor = this.forest.allocateCursor();
|
|
306
|
+
this.forest.moveCursorToPath(path, cursor);
|
|
307
|
+
assert(
|
|
308
|
+
cursor.mode === CursorLocationType.Nodes,
|
|
309
|
+
0xa9b /* attach should happen in a node */,
|
|
310
|
+
);
|
|
311
|
+
cursor.exitNode();
|
|
312
|
+
// TODO ADO:36390 avoid re-indexing the whole field when not necessary
|
|
313
|
+
this.indexField(cursor);
|
|
318
314
|
if (!this.isShallowIndex) {
|
|
319
|
-
const cursor = this.forest.allocateCursor();
|
|
320
|
-
this.forest.moveCursorToPath(path, cursor);
|
|
321
|
-
assert(
|
|
322
|
-
cursor.mode === CursorLocationType.Nodes,
|
|
323
|
-
0xa9b /* attach should happen in a node */,
|
|
324
|
-
);
|
|
325
|
-
cursor.exitNode();
|
|
326
315
|
this.indexSpine(cursor);
|
|
327
|
-
cursor.clear();
|
|
328
316
|
}
|
|
317
|
+
cursor.clear();
|
|
329
318
|
}
|
|
330
319
|
|
|
331
320
|
private checkNotDisposed(errorMessage?: string): void {
|
|
@@ -236,20 +236,6 @@ export class ObjectForest implements IEditableForest {
|
|
|
236
236
|
parent.fields.delete(key);
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
public replace(
|
|
240
|
-
newContentSource: FieldKey,
|
|
241
|
-
range: Range,
|
|
242
|
-
oldContentDestination: FieldKey,
|
|
243
|
-
oldContentId: DeltaDetachedNodeId,
|
|
244
|
-
): void {
|
|
245
|
-
preEdit();
|
|
246
|
-
assert(
|
|
247
|
-
newContentSource !== oldContentDestination,
|
|
248
|
-
0x7ba /* Replace detached source field and detached destination field must be different */,
|
|
249
|
-
);
|
|
250
|
-
this.detachEdit(range, oldContentDestination);
|
|
251
|
-
this.attachEdit(newContentSource, range.end - range.start, range.start);
|
|
252
|
-
}
|
|
253
239
|
public enterNode(index: number): void {
|
|
254
240
|
cursor.enterNode(index);
|
|
255
241
|
}
|
|
@@ -267,10 +253,7 @@ export class ObjectForest implements IEditableForest {
|
|
|
267
253
|
const forestVisitor = new Visitor(this);
|
|
268
254
|
const announcedVisitors: AnnouncedVisitor[] = [];
|
|
269
255
|
this.deltaVisitors.forEach((getVisitor) => announcedVisitors.push(getVisitor()));
|
|
270
|
-
const combinedVisitor = combineVisitors(
|
|
271
|
-
[forestVisitor, ...announcedVisitors],
|
|
272
|
-
announcedVisitors,
|
|
273
|
-
);
|
|
256
|
+
const combinedVisitor = combineVisitors([forestVisitor, ...announcedVisitors]);
|
|
274
257
|
this.activeVisitor = combinedVisitor;
|
|
275
258
|
return combinedVisitor;
|
|
276
259
|
}
|