@fluidframework/tree 2.93.0 → 2.100.1
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 +47 -0
- package/INCREMENTAL_SUMMARY.md +89 -0
- package/README.md +6 -0
- package/api-report/tree.alpha.api.md +6 -1
- package/api-report/tree.beta.api.md +3 -1
- package/api-report/tree.legacy.beta.api.md +3 -1
- package/dist/core/change-family/changeFamily.d.ts +23 -0
- package/dist/core/change-family/changeFamily.d.ts.map +1 -1
- package/dist/core/change-family/changeFamily.js.map +1 -1
- package/dist/core/tree/detachedFieldIndexCodecV1.d.ts.map +1 -1
- package/dist/core/tree/detachedFieldIndexCodecV1.js +2 -0
- package/dist/core/tree/detachedFieldIndexCodecV1.js.map +1 -1
- package/dist/core/tree/detachedFieldIndexCodecV2.d.ts.map +1 -1
- package/dist/core/tree/detachedFieldIndexCodecV2.js +2 -0
- package/dist/core/tree/detachedFieldIndexCodecV2.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/basicChunk.d.ts +25 -1
- package/dist/feature-libraries/chunked-forest/basicChunk.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/basicChunk.js +71 -18
- package/dist/feature-libraries/chunked-forest/basicChunk.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/chunkDecoding.d.ts +13 -4
- package/dist/feature-libraries/chunked-forest/codec/chunkDecoding.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/chunkDecoding.js +31 -4
- package/dist/feature-libraries/chunked-forest/codec/chunkDecoding.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts +27 -0
- package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/codecs.js +4 -1
- package/dist/feature-libraries/chunked-forest/codec/codecs.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 +1 -1
- package/dist/feature-libraries/chunked-forest/codec/compressedEncode.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/format/formatGeneric.d.ts +1 -2
- package/dist/feature-libraries/chunked-forest/codec/format/formatGeneric.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/format/formatGeneric.js +0 -1
- package/dist/feature-libraries/chunked-forest/codec/format/formatGeneric.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/format/index.d.ts +1 -1
- package/dist/feature-libraries/chunked-forest/codec/format/index.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/format/index.js +2 -1
- package/dist/feature-libraries/chunked-forest/codec/format/index.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/format/versions.d.ts +10 -2
- package/dist/feature-libraries/chunked-forest/codec/format/versions.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/format/versions.js +15 -1
- package/dist/feature-libraries/chunked-forest/codec/format/versions.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/uniformChunk.d.ts +6 -1
- package/dist/feature-libraries/chunked-forest/uniformChunk.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/uniformChunk.js +25 -1
- package/dist/feature-libraries/chunked-forest/uniformChunk.js.map +1 -1
- package/dist/feature-libraries/forest-summary/incrementalSummaryBuilder.d.ts +19 -0
- package/dist/feature-libraries/forest-summary/incrementalSummaryBuilder.d.ts.map +1 -1
- package/dist/feature-libraries/forest-summary/incrementalSummaryBuilder.js +76 -22
- package/dist/feature-libraries/forest-summary/incrementalSummaryBuilder.js.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeCodecV1.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeCodecV1.js +4 -0
- package/dist/feature-libraries/modular-schema/modularChangeCodecV1.js.map +1 -1
- 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/shared-tree/independentView.d.ts.map +1 -1
- package/dist/shared-tree/independentView.js +2 -0
- package/dist/shared-tree/independentView.js.map +1 -1
- package/dist/shared-tree/sharedTree.d.ts +35 -1
- package/dist/shared-tree/sharedTree.d.ts.map +1 -1
- package/dist/shared-tree/sharedTree.js +6 -0
- package/dist/shared-tree/sharedTree.js.map +1 -1
- package/dist/shared-tree/sharedTreeChangeCodecs.js +1 -0
- package/dist/shared-tree/sharedTreeChangeCodecs.js.map +1 -1
- package/dist/shared-tree/treeAlpha.d.ts.map +1 -1
- package/dist/shared-tree/treeAlpha.js +2 -0
- package/dist/shared-tree/treeAlpha.js.map +1 -1
- package/dist/shared-tree/treeCheckout.d.ts +1 -1
- package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
- package/dist/shared-tree/treeCheckout.js +2 -0
- package/dist/shared-tree/treeCheckout.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecs.d.ts +3 -0
- package/dist/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerCodecs.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsCommons.d.ts +14 -0
- package/dist/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsCommons.js +14 -0
- package/dist/shared-tree-core/editManagerCodecsCommons.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts +3 -0
- package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsVSharedBranches.d.ts +3 -0
- package/dist/shared-tree-core/editManagerCodecsVSharedBranches.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerCodecsVSharedBranches.js.map +1 -1
- package/dist/shared-tree-core/editManagerSummarizer.d.ts +9 -1
- package/dist/shared-tree-core/editManagerSummarizer.d.ts.map +1 -1
- package/dist/shared-tree-core/editManagerSummarizer.js +20 -5
- package/dist/shared-tree-core/editManagerSummarizer.js.map +1 -1
- package/dist/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -1
- package/dist/shared-tree-core/messageCodecV1ToV4.js +4 -0
- package/dist/shared-tree-core/messageCodecV1ToV4.js.map +1 -1
- package/dist/shared-tree-core/messageCodecVSharedBranches.d.ts.map +1 -1
- package/dist/shared-tree-core/messageCodecVSharedBranches.js +4 -0
- package/dist/shared-tree-core/messageCodecVSharedBranches.js.map +1 -1
- package/dist/shared-tree-core/sharedTreeCore.d.ts +4 -0
- package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
- package/dist/shared-tree-core/sharedTreeCore.js +1 -1
- package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
- package/dist/simple-tree/api/schemaFactoryAlpha.d.ts +17 -1
- package/dist/simple-tree/api/schemaFactoryAlpha.d.ts.map +1 -1
- package/dist/simple-tree/api/schemaFactoryAlpha.js +9 -0
- package/dist/simple-tree/api/schemaFactoryAlpha.js.map +1 -1
- package/docs/user-facing/isolated-declarations.md +147 -0
- package/lib/core/change-family/changeFamily.d.ts +23 -0
- package/lib/core/change-family/changeFamily.d.ts.map +1 -1
- package/lib/core/change-family/changeFamily.js.map +1 -1
- package/lib/core/tree/detachedFieldIndexCodecV1.d.ts.map +1 -1
- package/lib/core/tree/detachedFieldIndexCodecV1.js +2 -0
- package/lib/core/tree/detachedFieldIndexCodecV1.js.map +1 -1
- package/lib/core/tree/detachedFieldIndexCodecV2.d.ts.map +1 -1
- package/lib/core/tree/detachedFieldIndexCodecV2.js +2 -0
- package/lib/core/tree/detachedFieldIndexCodecV2.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/basicChunk.d.ts +25 -1
- package/lib/feature-libraries/chunked-forest/basicChunk.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/basicChunk.js +72 -19
- package/lib/feature-libraries/chunked-forest/basicChunk.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/chunkDecoding.d.ts +13 -4
- package/lib/feature-libraries/chunked-forest/codec/chunkDecoding.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/chunkDecoding.js +32 -5
- package/lib/feature-libraries/chunked-forest/codec/chunkDecoding.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts +27 -0
- package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/codecs.js +5 -2
- package/lib/feature-libraries/chunked-forest/codec/codecs.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 +2 -2
- package/lib/feature-libraries/chunked-forest/codec/compressedEncode.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/format/formatGeneric.d.ts +1 -2
- package/lib/feature-libraries/chunked-forest/codec/format/formatGeneric.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/format/formatGeneric.js +0 -1
- package/lib/feature-libraries/chunked-forest/codec/format/formatGeneric.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/format/index.d.ts +1 -1
- package/lib/feature-libraries/chunked-forest/codec/format/index.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/format/index.js +1 -1
- package/lib/feature-libraries/chunked-forest/codec/format/index.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/format/versions.d.ts +10 -2
- package/lib/feature-libraries/chunked-forest/codec/format/versions.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/format/versions.js +13 -0
- package/lib/feature-libraries/chunked-forest/codec/format/versions.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/uniformChunk.d.ts +6 -1
- package/lib/feature-libraries/chunked-forest/uniformChunk.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/uniformChunk.js +26 -2
- package/lib/feature-libraries/chunked-forest/uniformChunk.js.map +1 -1
- package/lib/feature-libraries/forest-summary/incrementalSummaryBuilder.d.ts +19 -0
- package/lib/feature-libraries/forest-summary/incrementalSummaryBuilder.d.ts.map +1 -1
- package/lib/feature-libraries/forest-summary/incrementalSummaryBuilder.js +76 -22
- package/lib/feature-libraries/forest-summary/incrementalSummaryBuilder.js.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeCodecV1.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeCodecV1.js +4 -0
- package/lib/feature-libraries/modular-schema/modularChangeCodecV1.js.map +1 -1
- 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/shared-tree/independentView.d.ts.map +1 -1
- package/lib/shared-tree/independentView.js +2 -0
- package/lib/shared-tree/independentView.js.map +1 -1
- package/lib/shared-tree/sharedTree.d.ts +35 -1
- package/lib/shared-tree/sharedTree.d.ts.map +1 -1
- package/lib/shared-tree/sharedTree.js +6 -0
- package/lib/shared-tree/sharedTree.js.map +1 -1
- package/lib/shared-tree/sharedTreeChangeCodecs.js +1 -0
- package/lib/shared-tree/sharedTreeChangeCodecs.js.map +1 -1
- package/lib/shared-tree/treeAlpha.d.ts.map +1 -1
- package/lib/shared-tree/treeAlpha.js +2 -0
- package/lib/shared-tree/treeAlpha.js.map +1 -1
- package/lib/shared-tree/treeCheckout.d.ts +1 -1
- package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
- package/lib/shared-tree/treeCheckout.js +2 -0
- package/lib/shared-tree/treeCheckout.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecs.d.ts +3 -0
- package/lib/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerCodecs.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsCommons.d.ts +14 -0
- package/lib/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsCommons.js +14 -0
- package/lib/shared-tree-core/editManagerCodecsCommons.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts +3 -0
- package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsVSharedBranches.d.ts +3 -0
- package/lib/shared-tree-core/editManagerCodecsVSharedBranches.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerCodecsVSharedBranches.js.map +1 -1
- package/lib/shared-tree-core/editManagerSummarizer.d.ts +9 -1
- package/lib/shared-tree-core/editManagerSummarizer.d.ts.map +1 -1
- package/lib/shared-tree-core/editManagerSummarizer.js +20 -5
- package/lib/shared-tree-core/editManagerSummarizer.js.map +1 -1
- package/lib/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -1
- package/lib/shared-tree-core/messageCodecV1ToV4.js +4 -0
- package/lib/shared-tree-core/messageCodecV1ToV4.js.map +1 -1
- package/lib/shared-tree-core/messageCodecVSharedBranches.d.ts.map +1 -1
- package/lib/shared-tree-core/messageCodecVSharedBranches.js +4 -0
- package/lib/shared-tree-core/messageCodecVSharedBranches.js.map +1 -1
- package/lib/shared-tree-core/sharedTreeCore.d.ts +4 -0
- package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
- package/lib/shared-tree-core/sharedTreeCore.js +1 -1
- package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
- package/lib/simple-tree/api/schemaFactoryAlpha.d.ts +17 -1
- package/lib/simple-tree/api/schemaFactoryAlpha.d.ts.map +1 -1
- package/lib/simple-tree/api/schemaFactoryAlpha.js +9 -0
- package/lib/simple-tree/api/schemaFactoryAlpha.js.map +1 -1
- package/package.json +24 -24
- package/src/core/change-family/changeFamily.ts +25 -0
- package/src/core/tree/detachedFieldIndexCodecV1.ts +2 -0
- package/src/core/tree/detachedFieldIndexCodecV2.ts +2 -0
- package/src/feature-libraries/chunked-forest/basicChunk.ts +76 -20
- package/src/feature-libraries/chunked-forest/codec/chunkDecoding.ts +61 -12
- package/src/feature-libraries/chunked-forest/codec/codecs.ts +32 -1
- package/src/feature-libraries/chunked-forest/codec/compressedEncode.ts +3 -2
- package/src/feature-libraries/chunked-forest/codec/format/formatGeneric.ts +0 -1
- package/src/feature-libraries/chunked-forest/codec/format/index.ts +1 -0
- package/src/feature-libraries/chunked-forest/codec/format/versions.ts +15 -0
- package/src/feature-libraries/chunked-forest/uniformChunk.ts +32 -2
- package/src/feature-libraries/forest-summary/incrementalSummaryBuilder.ts +116 -31
- package/src/feature-libraries/modular-schema/modularChangeCodecV1.ts +4 -0
- package/src/packageVersion.ts +1 -1
- package/src/shared-tree/independentView.ts +2 -0
- package/src/shared-tree/sharedTree.ts +41 -1
- package/src/shared-tree/sharedTreeChangeCodecs.ts +1 -0
- package/src/shared-tree/treeAlpha.ts +2 -0
- package/src/shared-tree/treeCheckout.ts +2 -0
- package/src/shared-tree-core/editManagerCodecs.ts +3 -0
- package/src/shared-tree-core/editManagerCodecsCommons.ts +29 -0
- package/src/shared-tree-core/editManagerCodecsV1toV4.ts +3 -0
- package/src/shared-tree-core/editManagerCodecsVSharedBranches.ts +3 -0
- package/src/shared-tree-core/editManagerSummarizer.ts +17 -5
- package/src/shared-tree-core/messageCodecV1ToV4.ts +4 -0
- package/src/shared-tree-core/messageCodecVSharedBranches.ts +5 -1
- package/src/shared-tree-core/sharedTreeCore.ts +8 -1
- package/src/simple-tree/api/schemaFactoryAlpha.ts +34 -3
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { assert, oob, fail } from "@fluidframework/core-utils/internal";
|
|
6
|
+
import { assert, oob, fail, debugAssert } from "@fluidframework/core-utils/internal";
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
CursorLocationType,
|
|
@@ -166,10 +166,19 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
166
166
|
if (this.nestedCursor !== undefined) {
|
|
167
167
|
return this.nestedCursor.mode;
|
|
168
168
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
this.assertChunkStacksMatchNodeDepth();
|
|
170
|
+
return this.siblingStack.length % 2 === 0
|
|
171
|
+
? CursorLocationType.Fields
|
|
172
|
+
: CursorLocationType.Nodes;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Asserts that the node-only stacks (`indexOfChunkStack` and `indexWithinChunkStack`) are in sync with `siblingStack`.
|
|
177
|
+
* Since `siblingStack` interleaves field and node levels while the node-only stacks are pushed/popped only on node-level transitions,
|
|
178
|
+
* their length should always equal the number of node levels traversed.
|
|
179
|
+
*/
|
|
180
|
+
private assertChunkStacksMatchNodeDepth(): void {
|
|
181
|
+
const halfHeight = this.getNodeOnlyHeightFromHeight();
|
|
173
182
|
assert(
|
|
174
183
|
this.indexOfChunkStack.length === halfHeight,
|
|
175
184
|
0x51c /* unexpected indexOfChunkStack */,
|
|
@@ -178,9 +187,6 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
178
187
|
this.indexWithinChunkStack.length === halfHeight,
|
|
179
188
|
0x51d /* unexpected indexWithinChunkStack */,
|
|
180
189
|
);
|
|
181
|
-
return this.siblingStack.length % 2 === 0
|
|
182
|
-
? CursorLocationType.Fields
|
|
183
|
-
: CursorLocationType.Nodes;
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
public getFieldKey(): FieldKey {
|
|
@@ -203,9 +209,32 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
203
209
|
return this.indexStack[height] ?? oob();
|
|
204
210
|
}
|
|
205
211
|
|
|
206
|
-
private
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
private getStackedChunkIndex(height: number): number {
|
|
213
|
+
assert(height % 2 === 1, 0xcf3 /* must be node height */);
|
|
214
|
+
assert(height >= 0, 0xcf4 /* must not be above root */);
|
|
215
|
+
return this.indexOfChunkStack[this.getNodeOnlyHeightFromHeight(height)] ?? oob();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private getStackedChunk(height: number): BasicChunk {
|
|
219
|
+
const index = this.getStackedChunkIndex(height);
|
|
220
|
+
const chunk = (this.siblingStack[height] as readonly TreeChunk[])[index];
|
|
221
|
+
debugAssert(() => chunk instanceof BasicChunk || "only basic chunks are expected");
|
|
222
|
+
return chunk as BasicChunk;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Converts a {@link height}, which contains field and node levels, into the corresponding depth/index
|
|
227
|
+
* for the node-only stacks ({@link indexOfChunkStack} and {@link indexWithinChunkStack}), which are
|
|
228
|
+
* only pushed on node-level transitions.
|
|
229
|
+
*
|
|
230
|
+
* @param height - A depth in {@link siblingStack} to convert. Defaults to {@link siblingStack}'s
|
|
231
|
+
* current length, which gives the current depth of the node-only stacks.
|
|
232
|
+
* @returns `floor(height / 2)` — the number of node levels at or below the given stack height.
|
|
233
|
+
*/
|
|
234
|
+
private getNodeOnlyHeightFromHeight(height: number = this.siblingStack.length): number {
|
|
235
|
+
// The bitwise shift computes the floor, which is valid assuming the depth is less than 2^31, which seems safe.
|
|
236
|
+
// eslint-disable-next-line no-bitwise
|
|
237
|
+
return height >> 1;
|
|
209
238
|
}
|
|
210
239
|
|
|
211
240
|
public getFieldLength(): number {
|
|
@@ -322,6 +351,11 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
322
351
|
assert(this.mode === CursorLocationType.Nodes, 0x528 /* must be in nodes mode */);
|
|
323
352
|
this.siblingStack.push(this.siblings);
|
|
324
353
|
this.indexStack.push(this.index);
|
|
354
|
+
// Save the chunk array position of the current node. When siblings contain
|
|
355
|
+
// multi node chunks, the flat node index diverges from the array position,
|
|
356
|
+
// so getField needs this to locate the parent in the sibling array.
|
|
357
|
+
this.indexOfChunkStack.push(this.indexOfChunk);
|
|
358
|
+
this.indexWithinChunkStack.push(this.indexWithinChunk);
|
|
325
359
|
|
|
326
360
|
// For fields, siblings are only used for key lookup and
|
|
327
361
|
// nextField and which has arbitrary iteration order,
|
|
@@ -330,6 +364,7 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
330
364
|
// at the cost of an allocation here.
|
|
331
365
|
this.index = 0;
|
|
332
366
|
this.siblings = [key];
|
|
367
|
+
this.assertChunkStacksMatchNodeDepth();
|
|
333
368
|
}
|
|
334
369
|
|
|
335
370
|
public nextField(): boolean {
|
|
@@ -355,8 +390,11 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
355
390
|
|
|
356
391
|
this.siblingStack.push(this.siblings);
|
|
357
392
|
this.indexStack.push(this.index);
|
|
393
|
+
this.indexOfChunkStack.push(this.indexOfChunk);
|
|
394
|
+
this.indexWithinChunkStack.push(this.indexWithinChunk);
|
|
358
395
|
this.index = 0;
|
|
359
396
|
this.siblings = [...fields.keys()]; // TODO: avoid this copy
|
|
397
|
+
this.assertChunkStacksMatchNodeDepth();
|
|
360
398
|
return true;
|
|
361
399
|
}
|
|
362
400
|
|
|
@@ -422,12 +460,11 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
422
460
|
}
|
|
423
461
|
this.siblingStack.push(this.siblings);
|
|
424
462
|
this.indexStack.push(this.index);
|
|
425
|
-
this.indexOfChunkStack.push(this.indexOfChunk);
|
|
426
|
-
this.indexWithinChunkStack.push(this.indexWithinChunk);
|
|
427
463
|
this.index = 0;
|
|
428
464
|
this.siblings = siblings;
|
|
429
465
|
this.indexOfChunk = 0;
|
|
430
466
|
this.indexWithinChunk = 0;
|
|
467
|
+
this.assertChunkStacksMatchNodeDepth();
|
|
431
468
|
this.initNestedCursor();
|
|
432
469
|
return true;
|
|
433
470
|
}
|
|
@@ -486,6 +523,12 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
486
523
|
this.siblings =
|
|
487
524
|
this.siblingStack.pop() ?? fail(0xaf0 /* Unexpected siblingStack.length */);
|
|
488
525
|
this.index = this.indexStack.pop() ?? fail(0xaf1 /* Unexpected indexStack.length */);
|
|
526
|
+
this.indexOfChunk =
|
|
527
|
+
this.indexOfChunkStack.pop() ?? fail(0xcf5 /* Unexpected indexOfChunkStack.length */);
|
|
528
|
+
this.indexWithinChunk =
|
|
529
|
+
this.indexWithinChunkStack.pop() ??
|
|
530
|
+
fail(0xcf6 /* Unexpected indexWithinChunkStack.length */);
|
|
531
|
+
this.assertChunkStacksMatchNodeDepth();
|
|
489
532
|
}
|
|
490
533
|
|
|
491
534
|
public exitNode(): void {
|
|
@@ -502,18 +545,27 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
502
545
|
this.siblings =
|
|
503
546
|
this.siblingStack.pop() ?? fail(0xaf2 /* Unexpected siblingStack.length */);
|
|
504
547
|
this.index = this.indexStack.pop() ?? fail(0xaf3 /* Unexpected indexStack.length */);
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
this.
|
|
508
|
-
|
|
509
|
-
|
|
548
|
+
// At the Fields level these aren't semantically used, but reset for consistent state
|
|
549
|
+
// (so a fully-iterated cursor matches a fresh cursor at the same logical position).
|
|
550
|
+
this.indexOfChunk = 0;
|
|
551
|
+
this.indexWithinChunk = 0;
|
|
552
|
+
this.assertChunkStacksMatchNodeDepth();
|
|
510
553
|
}
|
|
511
554
|
|
|
512
555
|
private getNode(): BasicChunk {
|
|
513
556
|
assert(this.mode === CursorLocationType.Nodes, 0x52f /* can only get node when in node */);
|
|
514
|
-
|
|
557
|
+
const chunk = (this.siblings as TreeChunk[])[this.indexOfChunk];
|
|
558
|
+
debugAssert(() => chunk instanceof BasicChunk || "only basic chunks are expected");
|
|
559
|
+
return chunk as BasicChunk;
|
|
515
560
|
}
|
|
516
561
|
|
|
562
|
+
/**
|
|
563
|
+
* Resolves the chunks that make up the field the cursor is currently in. At the root, this is
|
|
564
|
+
* {@link root} directly. Otherwise, the cursor must be in {@link CursorLocationType.Fields} mode,
|
|
565
|
+
* and the result is looked up on the parent node using the current field key.
|
|
566
|
+
*
|
|
567
|
+
* @returns The chunks that make up the field the cursor is currently in.
|
|
568
|
+
*/
|
|
517
569
|
private getField(): readonly TreeChunk[] {
|
|
518
570
|
if (this.siblingStack.length === 0) {
|
|
519
571
|
return this.root;
|
|
@@ -522,7 +574,11 @@ export class BasicChunkCursor extends SynchronousCursor implements ChunkedCursor
|
|
|
522
574
|
this.mode === CursorLocationType.Fields,
|
|
523
575
|
0x530 /* can only get field when in fields */,
|
|
524
576
|
);
|
|
525
|
-
|
|
577
|
+
// The parent node is the `BasicChunk` in the node array at the top of
|
|
578
|
+
// `siblingStack` while we are in `CursorLocationType.Fields` mode. We need the parent
|
|
579
|
+
// since a field's chunks are stored on the parent node's `BasicChunk.fields` map, not on
|
|
580
|
+
// the cursor itself.
|
|
581
|
+
const parent = this.getStackedChunk(this.siblingStack.length - 1);
|
|
526
582
|
const key: FieldKey = this.getFieldKey();
|
|
527
583
|
const field = parent.fields.get(key) ?? [];
|
|
528
584
|
return field;
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
OpSpaceCompressedId,
|
|
10
10
|
SessionId,
|
|
11
11
|
} from "@fluidframework/id-compressor";
|
|
12
|
+
import { v5 as uuidV5 } from "uuid";
|
|
12
13
|
|
|
13
14
|
import { DiscriminatedUnionDispatcher } from "../../../codec/index.js";
|
|
14
15
|
import type {
|
|
@@ -37,7 +38,8 @@ import {
|
|
|
37
38
|
decode as genericDecode,
|
|
38
39
|
readStreamIdentifier,
|
|
39
40
|
} from "./chunkDecodingGeneric.js";
|
|
40
|
-
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Referenced by doc comments
|
|
42
|
+
import type { FieldBatchEncodingContext, IncrementalDecoder } from "./codecs.js";
|
|
41
43
|
import {
|
|
42
44
|
type EncodedAnyShape,
|
|
43
45
|
type EncodedChunkShapeV1OrV2,
|
|
@@ -49,8 +51,8 @@ import {
|
|
|
49
51
|
type EncodedNestedArrayShape,
|
|
50
52
|
type EncodedNodeShape,
|
|
51
53
|
type EncodedValueShape,
|
|
52
|
-
FieldBatchFormatVersion,
|
|
53
54
|
SpecialField,
|
|
55
|
+
supportsIncrementalEncoding,
|
|
54
56
|
} from "./format/index.js";
|
|
55
57
|
|
|
56
58
|
export interface IdDecodingContext {
|
|
@@ -59,13 +61,32 @@ export interface IdDecodingContext {
|
|
|
59
61
|
* The creator of any local Ids to be decoded.
|
|
60
62
|
*/
|
|
61
63
|
originatorId: SessionId;
|
|
64
|
+
/**
|
|
65
|
+
* {@inheritdoc FieldBatchEncodingContext.isSummary}
|
|
66
|
+
*/
|
|
67
|
+
isSummary: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* See {@link FieldBatchEncodingContext.healUnresolvableIdentifiersOnDecode}.
|
|
70
|
+
*/
|
|
71
|
+
healUnresolvableIdentifiersOnDecode?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* See {@link FieldBatchEncodingContext.sharedObjectId}.
|
|
74
|
+
*/
|
|
75
|
+
sharedObjectId?: string;
|
|
62
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Random v4 UUID generated as a namespace for the "heal an unresolvable identifier into a stable UUID"
|
|
80
|
+
* path in {@link readValue}. This scheme requires consensus across all clients to function.
|
|
81
|
+
*/
|
|
82
|
+
const healingNamespace = "f8a89df3-6882-400f-b913-4c1f6f0157bd";
|
|
83
|
+
|
|
63
84
|
/**
|
|
64
85
|
* Decode `chunk` into a TreeChunk.
|
|
65
86
|
*/
|
|
66
87
|
export function decode(
|
|
67
88
|
chunk: EncodedFieldBatchV1OrV2,
|
|
68
|
-
idDecodingContext:
|
|
89
|
+
idDecodingContext: IdDecodingContext,
|
|
69
90
|
incrementalDecoder?: IncrementalDecoder,
|
|
70
91
|
): TreeChunk[] {
|
|
71
92
|
return genericDecode(
|
|
@@ -126,15 +147,43 @@ export function readValue(
|
|
|
126
147
|
typeof streamValue === "number" || typeof streamValue === "string",
|
|
127
148
|
0x997 /* identifier must be string or number. */,
|
|
128
149
|
);
|
|
150
|
+
if (typeof streamValue === "string") {
|
|
151
|
+
return streamValue;
|
|
152
|
+
}
|
|
129
153
|
const idCompressor = idDecodingContext.idCompressor;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
154
|
+
// OpSpaceCompressedIds are negative, and require a session-id to compute their value.
|
|
155
|
+
// Due to a bug, we have some special casing for them (see below).
|
|
156
|
+
// TODO: isFinalId should probably be exported from id-compressor and that could be used to do the narrowing here.
|
|
157
|
+
if (idDecodingContext.isSummary === true && streamValue < 0) {
|
|
158
|
+
if (
|
|
159
|
+
idDecodingContext.healUnresolvableIdentifiersOnDecode === true &&
|
|
160
|
+
idDecodingContext.sharedObjectId !== undefined
|
|
161
|
+
) {
|
|
162
|
+
// Documents written before the encode-side fix for non-finalized identifier
|
|
163
|
+
// values can persist negative op-space IDs that are no
|
|
164
|
+
// longer resolvable once the originating session's local state has been stripped.
|
|
165
|
+
// When loading such a summary with the heal-on-decode option on, synthesize a deterministic
|
|
166
|
+
// stable UUID so all readers of the same blob agree on the resulting value.
|
|
167
|
+
//
|
|
168
|
+
// The heal path is intentionally restricted to summary loads — an
|
|
169
|
+
// unresolvable ID encountered while applying an op should still surface as
|
|
170
|
+
// an error, since it indicates a real bug rather than a recoverable state.
|
|
171
|
+
return uuidV5(
|
|
172
|
+
`${idDecodingContext.sharedObjectId}|${streamValue}`,
|
|
173
|
+
healingNamespace,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
// See `SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode` for details on this error.
|
|
177
|
+
throw new Error(
|
|
178
|
+
"Summary could not be loaded due incorrectly encoded identifier. See SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode for mitigation.",
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return idCompressor.decompress(
|
|
182
|
+
idCompressor.normalizeToSessionSpace(
|
|
183
|
+
streamValue as OpSpaceCompressedId,
|
|
184
|
+
idDecodingContext.originatorId,
|
|
185
|
+
),
|
|
186
|
+
);
|
|
138
187
|
} else {
|
|
139
188
|
// EncodedCounter case:
|
|
140
189
|
unreachableCase(shape, "decoding values as deltas is not yet supported");
|
|
@@ -256,7 +305,7 @@ export class IncrementalChunkDecoder implements ChunkDecoder {
|
|
|
256
305
|
|
|
257
306
|
const chunkDecoder = (batch: EncodedFieldBatchV2): TreeChunk => {
|
|
258
307
|
assert(
|
|
259
|
-
batch.version
|
|
308
|
+
supportsIncrementalEncoding(batch.version),
|
|
260
309
|
0xc9f /* Unsupported FieldBatchFormatVersion for incremental chunks; must be v2 or higher */,
|
|
261
310
|
);
|
|
262
311
|
const context = new DecoderContext(
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
EncodedFieldBatchV1,
|
|
31
31
|
EncodedFieldBatchV2,
|
|
32
32
|
FieldBatchFormatVersion,
|
|
33
|
+
supportsIncrementalEncoding,
|
|
33
34
|
type EncodedFieldBatchV1OrV2,
|
|
34
35
|
} from "./format/index.js";
|
|
35
36
|
import type { IncrementalEncodingPolicy } from "./incrementalEncodingPolicy.js";
|
|
@@ -105,6 +106,33 @@ export interface FieldBatchEncodingContext {
|
|
|
105
106
|
* This will be defined if incremental encoding is supported and enabled.
|
|
106
107
|
*/
|
|
107
108
|
readonly incrementalEncoderDecoder?: IncrementalEncoderDecoder;
|
|
109
|
+
/**
|
|
110
|
+
* `true` when encoding to or decoding from a summary blob. `false` for
|
|
111
|
+
* op-stream encode/decode paths and for utility encoders that are not
|
|
112
|
+
* tied to a persisted document. Healing behavior is gated on this flag.
|
|
113
|
+
*/
|
|
114
|
+
readonly isSummary: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* If `true`, when an op-space compressed ID encountered while decoding
|
|
117
|
+
* cannot be resolved by the local id-compressor (e.g. the attach-summary
|
|
118
|
+
* blob's originator session state was stripped), a deterministic stable
|
|
119
|
+
* UUID derived from `sharedObjectId` is returned instead of throwing.
|
|
120
|
+
* @remarks
|
|
121
|
+
* Off by default. Used only to recover documents whose attach summary was
|
|
122
|
+
* written with non-finalized op-space IDs before the encode-side fix
|
|
123
|
+
* shipped. Only takes effect when `isSummary` is also `true`.
|
|
124
|
+
* See {@link SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode}.
|
|
125
|
+
*/
|
|
126
|
+
readonly healUnresolvableIdentifiersOnDecode?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* The SharedTree's shared-object id, used as input to the deterministic
|
|
129
|
+
* UUID derivation when `healUnresolvableIdentifiersOnDecode` triggers. Required
|
|
130
|
+
* for that path; ignored otherwise.
|
|
131
|
+
* @remarks
|
|
132
|
+
* This allows us to ensure that multiple attaches,
|
|
133
|
+
* in the same or different documents, with the same session offsets, get different UUIDs.
|
|
134
|
+
*/
|
|
135
|
+
readonly sharedObjectId?: string;
|
|
108
136
|
}
|
|
109
137
|
/**
|
|
110
138
|
* @remarks
|
|
@@ -147,7 +175,7 @@ function makeFieldBatchCodecForVersion(
|
|
|
147
175
|
}
|
|
148
176
|
case TreeCompressionStrategy.CompressedIncremental: {
|
|
149
177
|
assert(
|
|
150
|
-
version
|
|
178
|
+
supportsIncrementalEncoding(version),
|
|
151
179
|
0xca0 /* Unsupported FieldBatchFormatVersion for incremental encoding; must be v2 or higher */,
|
|
152
180
|
);
|
|
153
181
|
// Incremental encoding is only supported for CompressedIncremental.
|
|
@@ -189,6 +217,9 @@ function makeFieldBatchCodecForVersion(
|
|
|
189
217
|
{
|
|
190
218
|
idCompressor: context.idCompressor,
|
|
191
219
|
originatorId: context.originatorId,
|
|
220
|
+
isSummary: context.isSummary,
|
|
221
|
+
healUnresolvableIdentifiersOnDecode: context.healUnresolvableIdentifiersOnDecode,
|
|
222
|
+
sharedObjectId: context.sharedObjectId,
|
|
192
223
|
},
|
|
193
224
|
context.incrementalEncoderDecoder,
|
|
194
225
|
).map((chunk) => chunk.cursor());
|
|
@@ -36,8 +36,9 @@ import {
|
|
|
36
36
|
type EncodedFieldBatchV1OrV2,
|
|
37
37
|
type EncodedNestedArrayShape,
|
|
38
38
|
type EncodedValueShape,
|
|
39
|
-
FieldBatchFormatVersion,
|
|
39
|
+
type FieldBatchFormatVersion,
|
|
40
40
|
SpecialField,
|
|
41
|
+
supportsIncrementalEncoding,
|
|
41
42
|
} from "./format/index.js";
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -461,7 +462,7 @@ export const incrementalFieldEncoder: FieldEncoder = {
|
|
|
461
462
|
0xc88 /* incremental encoder must be defined to use incrementalFieldEncoder */,
|
|
462
463
|
);
|
|
463
464
|
assert(
|
|
464
|
-
context.version
|
|
465
|
+
supportsIncrementalEncoding(context.version),
|
|
465
466
|
0xca1 /* Unsupported FieldBatchFormatVersion for incremental encoding; must be v2 or higher */,
|
|
466
467
|
);
|
|
467
468
|
|
|
@@ -31,6 +31,21 @@ export const FieldBatchFormatVersion = strictEnum("FieldBatchFormatVersion", {
|
|
|
31
31
|
v2: 2,
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Whether the given format version supports incremental chunk encoding.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* This helper should be used for comparison since experimental versions
|
|
39
|
+
* can be a string.
|
|
40
|
+
*/
|
|
41
|
+
export function supportsIncrementalEncoding(version: FieldBatchFormatVersion): boolean {
|
|
42
|
+
if (version === FieldBatchFormatVersion.v1) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
/**
|
|
35
50
|
* Encoded {@link FieldBatch} using V1 format.
|
|
36
51
|
* @remarks
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
cursorChunk,
|
|
21
21
|
dummyRoot,
|
|
22
22
|
} from "../../core/index.js";
|
|
23
|
-
import { ReferenceCountedBase, hasSome } from "../../util/index.js";
|
|
23
|
+
import { ReferenceCountedBase, getOrCreate, hasSome } from "../../util/index.js";
|
|
24
24
|
import { SynchronousCursor, prefixFieldPath, prefixPath } from "../treeCursorUtils.js";
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -83,6 +83,23 @@ export class UniformChunk extends ReferenceCountedBase implements TreeChunk {
|
|
|
83
83
|
*/
|
|
84
84
|
export type FieldShape = readonly [FieldKey, TreeShape, number];
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Maximum topLevelLength value (exclusive) for which {@link TreeShape.withTopLevelLength}
|
|
88
|
+
* caches the resulting {@link ChunkShape}. Values at or above this threshold always
|
|
89
|
+
* create a new instance to prevent unbounded cache growth.
|
|
90
|
+
*
|
|
91
|
+
* @remarks
|
|
92
|
+
* This value is an estimation of the general size needed to cover current workflows,
|
|
93
|
+
* not a researched constant, and is safe to tune as workloads change.
|
|
94
|
+
*
|
|
95
|
+
* Raising this value captures more chunk sizes in the cache, at the cost of
|
|
96
|
+
* each `TreeShape` retaining up to `chunkShapeCacheLimit - 1` cached entries for the
|
|
97
|
+
* lifetime of the shape. Lowering it reduces memory held per `TreeShape` but forces
|
|
98
|
+
* small chunks, where the relative cost of rebuilding `positions` is highest, to pay
|
|
99
|
+
* the construction cost on every call.
|
|
100
|
+
*/
|
|
101
|
+
const chunkShapeCacheLimit = 8;
|
|
102
|
+
|
|
86
103
|
/**
|
|
87
104
|
* The "shape" of a tree.
|
|
88
105
|
* Does not contain the actual values from the tree, but describes everything else,
|
|
@@ -110,7 +127,13 @@ export class TreeShape {
|
|
|
110
127
|
public readonly mayContainCompressedIds: boolean;
|
|
111
128
|
|
|
112
129
|
/**
|
|
113
|
-
*
|
|
130
|
+
* Cache for ChunkShape instances created by {@link withTopLevelLength}.
|
|
131
|
+
* `topLevelLength` is always a positive integer (enforced by the {@link ChunkShape} constructor),
|
|
132
|
+
* so the cache only ever holds entries for values in `1..chunkShapeCacheLimit - 1` to prevent unbounded growth.
|
|
133
|
+
*/
|
|
134
|
+
private readonly chunkShapeCache: Map<number, ChunkShape> = new Map();
|
|
135
|
+
|
|
136
|
+
/**
|
|
114
137
|
* @param type - {@link TreeNodeSchemaIdentifier} used to compare shapes.
|
|
115
138
|
* @param hasValue - whether or not the TreeShape has a value.
|
|
116
139
|
* @param fieldsArray - an array of {@link FieldShape} values, which contains a TreeShape for each FieldKey.
|
|
@@ -179,6 +202,13 @@ export class TreeShape {
|
|
|
179
202
|
}
|
|
180
203
|
|
|
181
204
|
public withTopLevelLength(topLevelLength: number): ChunkShape {
|
|
205
|
+
if (topLevelLength < chunkShapeCacheLimit) {
|
|
206
|
+
return getOrCreate(
|
|
207
|
+
this.chunkShapeCache,
|
|
208
|
+
topLevelLength,
|
|
209
|
+
() => new ChunkShape(this, topLevelLength),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
182
212
|
return new ChunkShape(this, topLevelLength);
|
|
183
213
|
}
|
|
184
214
|
}
|