@fluidframework/tree 2.93.0 → 2.101.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 +74 -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 +5 -2
- package/dist/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/compressedEncode.d.ts +10 -2
- package/dist/feature-libraries/chunked-forest/codec/compressedEncode.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/compressedEncode.js +7 -2
- 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/codec/nodeEncoder.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/nodeEncoder.js +9 -1
- package/dist/feature-libraries/chunked-forest/codec/nodeEncoder.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/schemaBasedEncode.d.ts +3 -3
- package/dist/feature-libraries/chunked-forest/codec/schemaBasedEncode.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/codec/schemaBasedEncode.js +8 -8
- package/dist/feature-libraries/chunked-forest/codec/schemaBasedEncode.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/genericFieldKindFormat.d.ts +2 -14
- package/dist/feature-libraries/modular-schema/genericFieldKindFormat.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/genericFieldKindFormat.js +1 -17
- package/dist/feature-libraries/modular-schema/genericFieldKindFormat.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/schemaFactory.d.ts +3 -3
- package/dist/simple-tree/api/schemaFactory.js +3 -3
- package/dist/simple-tree/api/schemaFactory.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/dist/tableSchema.d.ts.map +1 -1
- package/dist/tableSchema.js +102 -20
- package/dist/tableSchema.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 +6 -3
- package/lib/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/compressedEncode.d.ts +10 -2
- package/lib/feature-libraries/chunked-forest/codec/compressedEncode.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/compressedEncode.js +8 -3
- 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/codec/nodeEncoder.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/nodeEncoder.js +9 -1
- package/lib/feature-libraries/chunked-forest/codec/nodeEncoder.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/schemaBasedEncode.d.ts +3 -3
- package/lib/feature-libraries/chunked-forest/codec/schemaBasedEncode.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/codec/schemaBasedEncode.js +8 -8
- package/lib/feature-libraries/chunked-forest/codec/schemaBasedEncode.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/genericFieldKindFormat.d.ts +2 -14
- package/lib/feature-libraries/modular-schema/genericFieldKindFormat.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/genericFieldKindFormat.js +1 -17
- package/lib/feature-libraries/modular-schema/genericFieldKindFormat.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/schemaFactory.d.ts +3 -3
- package/lib/simple-tree/api/schemaFactory.js +3 -3
- package/lib/simple-tree/api/schemaFactory.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/lib/tableSchema.d.ts.map +1 -1
- package/lib/tableSchema.js +103 -21
- package/lib/tableSchema.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 +34 -1
- package/src/feature-libraries/chunked-forest/codec/compressedEncode.ts +9 -3
- 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/codec/nodeEncoder.ts +9 -1
- package/src/feature-libraries/chunked-forest/codec/schemaBasedEncode.ts +9 -1
- 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/genericFieldKindFormat.ts +3 -21
- 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/schemaFactory.ts +3 -3
- package/src/simple-tree/api/schemaFactoryAlpha.ts +34 -3
- package/src/tableSchema.ts +134 -35
|
@@ -7,6 +7,8 @@ import type { IIdCompressor, SessionId } from "@fluidframework/id-compressor";
|
|
|
7
7
|
|
|
8
8
|
import type { ICodecFamily, IJsonCodec } from "../../codec/index.js";
|
|
9
9
|
import type { SchemaAndPolicy } from "../../core/index.js";
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Referenced by doc comments
|
|
11
|
+
import type { FieldBatchEncodingContext } from "../../feature-libraries/index.js";
|
|
10
12
|
import type { JsonCompatibleReadOnly } from "../../util/index.js";
|
|
11
13
|
import type { ChangeRebaser, RevisionTag, TaggedChange } from "../rebase/index.js";
|
|
12
14
|
|
|
@@ -25,6 +27,29 @@ export interface ChangeEncodingContext {
|
|
|
25
27
|
readonly revision: RevisionTag | undefined;
|
|
26
28
|
readonly idCompressor: IIdCompressor;
|
|
27
29
|
readonly schema?: SchemaAndPolicy;
|
|
30
|
+
/**
|
|
31
|
+
* `true` when this context is encoding to or decoding from a summary blob.
|
|
32
|
+
* `false` when this context is for an op (or any other non-summary path,
|
|
33
|
+
* including utility encoders that aren't tied to persistence).
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* Used to gate decode-time recovery behavior — for example, healing of
|
|
37
|
+
* unresolvable identifier IDs — that should only run when loading a
|
|
38
|
+
* (possibly broken) attach-summary blob, never when applying ops.
|
|
39
|
+
*/
|
|
40
|
+
readonly isSummary: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* If `true`, identifier values that the local id-compressor cannot resolve
|
|
43
|
+
* during decode are healed into deterministic stable UUIDs instead of
|
|
44
|
+
* throwing. See {@link FieldBatchEncodingContext.healUnresolvableIdentifiersOnDecode}.
|
|
45
|
+
* Only takes effect when `isSummary` is also `true`.
|
|
46
|
+
*/
|
|
47
|
+
readonly healUnresolvableIdentifiersOnDecode?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The SharedTree's shared-object id, used as input to the deterministic
|
|
50
|
+
* UUID derivation when {@link healUnresolvableIdentifiersOnDecode} triggers.
|
|
51
|
+
*/
|
|
52
|
+
readonly sharedObjectId?: string;
|
|
28
53
|
}
|
|
29
54
|
|
|
30
55
|
export type ChangeFamilyCodec<TChange> = IJsonCodec<
|
|
@@ -61,6 +61,8 @@ class MajorCodec implements IJsonCodec<Major, EncodedRevisionTag> {
|
|
|
61
61
|
originatorId: this.revisionTagCodec.localSessionId,
|
|
62
62
|
idCompressor: this.idCompressor,
|
|
63
63
|
revision: undefined,
|
|
64
|
+
// DetachedFieldIndex codecs are only used by the summarizer.
|
|
65
|
+
isSummary: true,
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -49,6 +49,8 @@ class MajorCodec implements IJsonCodec<Major> {
|
|
|
49
49
|
originatorId: this.revisionTagCodec.localSessionId,
|
|
50
50
|
idCompressor: this.idCompressor,
|
|
51
51
|
revision: undefined,
|
|
52
|
+
// DetachedFieldIndex codecs are only used by the summarizer.
|
|
53
|
+
isSummary: true,
|
|
52
54
|
});
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -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
|
|
@@ -124,6 +152,7 @@ function makeFieldBatchCodecForVersion(
|
|
|
124
152
|
fieldBatch: FieldBatch,
|
|
125
153
|
idCompressor: IIdCompressor,
|
|
126
154
|
incrementalEncoder: IncrementalEncoder | undefined,
|
|
155
|
+
isSummary: boolean,
|
|
127
156
|
) => EncodedFieldBatchV1OrV2,
|
|
128
157
|
encodedFieldBatchType: TSchema,
|
|
129
158
|
): CodecAndSchema<FieldBatch, FieldBatchEncodingContext> {
|
|
@@ -147,7 +176,7 @@ function makeFieldBatchCodecForVersion(
|
|
|
147
176
|
}
|
|
148
177
|
case TreeCompressionStrategy.CompressedIncremental: {
|
|
149
178
|
assert(
|
|
150
|
-
version
|
|
179
|
+
supportsIncrementalEncoding(version),
|
|
151
180
|
0xca0 /* Unsupported FieldBatchFormatVersion for incremental encoding; must be v2 or higher */,
|
|
152
181
|
);
|
|
153
182
|
// Incremental encoding is only supported for CompressedIncremental.
|
|
@@ -166,6 +195,7 @@ function makeFieldBatchCodecForVersion(
|
|
|
166
195
|
data,
|
|
167
196
|
context.idCompressor,
|
|
168
197
|
incrementalEncoder,
|
|
198
|
+
context.isSummary,
|
|
169
199
|
);
|
|
170
200
|
}
|
|
171
201
|
|
|
@@ -189,6 +219,9 @@ function makeFieldBatchCodecForVersion(
|
|
|
189
219
|
{
|
|
190
220
|
idCompressor: context.idCompressor,
|
|
191
221
|
originatorId: context.originatorId,
|
|
222
|
+
isSummary: context.isSummary,
|
|
223
|
+
healUnresolvableIdentifiersOnDecode: context.healUnresolvableIdentifiersOnDecode,
|
|
224
|
+
sharedObjectId: context.sharedObjectId,
|
|
192
225
|
},
|
|
193
226
|
context.incrementalEncoderDecoder,
|
|
194
227
|
).map((chunk) => chunk.cursor());
|
|
@@ -26,7 +26,8 @@ import {
|
|
|
26
26
|
Shape as ShapeGeneric,
|
|
27
27
|
updateShapesAndIdentifiersEncoding,
|
|
28
28
|
} from "./chunkEncodingGeneric.js";
|
|
29
|
-
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Referenced by doc comments
|
|
30
|
+
import type { FieldBatchEncodingContext, IncrementalEncoder } from "./codecs.js";
|
|
30
31
|
import type { FieldBatch } from "./fieldBatch.js";
|
|
31
32
|
import {
|
|
32
33
|
type EncodedAnyShape,
|
|
@@ -36,8 +37,9 @@ import {
|
|
|
36
37
|
type EncodedFieldBatchV1OrV2,
|
|
37
38
|
type EncodedNestedArrayShape,
|
|
38
39
|
type EncodedValueShape,
|
|
39
|
-
FieldBatchFormatVersion,
|
|
40
|
+
type FieldBatchFormatVersion,
|
|
40
41
|
SpecialField,
|
|
42
|
+
supportsIncrementalEncoding,
|
|
41
43
|
} from "./format/index.js";
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -461,7 +463,7 @@ export const incrementalFieldEncoder: FieldEncoder = {
|
|
|
461
463
|
0xc88 /* incremental encoder must be defined to use incrementalFieldEncoder */,
|
|
462
464
|
);
|
|
463
465
|
assert(
|
|
464
|
-
context.version
|
|
466
|
+
supportsIncrementalEncoding(context.version),
|
|
465
467
|
0xca1 /* Unsupported FieldBatchFormatVersion for incremental encoding; must be v2 or higher */,
|
|
466
468
|
);
|
|
467
469
|
|
|
@@ -534,6 +536,10 @@ export class EncoderContext implements NodeEncodeBuilder, FieldEncodeBuilder {
|
|
|
534
536
|
*/
|
|
535
537
|
public readonly incrementalEncoder: IncrementalEncoder | undefined,
|
|
536
538
|
public readonly version: FieldBatchFormatVersion,
|
|
539
|
+
/**
|
|
540
|
+
* See {@link FieldBatchEncodingContext.isSummary}.
|
|
541
|
+
*/
|
|
542
|
+
public readonly isSummary: boolean,
|
|
537
543
|
) {}
|
|
538
544
|
|
|
539
545
|
public nodeEncoderFromSchema(schemaName: TreeNodeSchemaIdentifier): NodeEncoder {
|
|
@@ -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
|
|
@@ -77,7 +77,15 @@ export class NodeShapeBasedEncoder
|
|
|
77
77
|
if (isStableId(cursor.value)) {
|
|
78
78
|
const sessionSpaceCompressedId = context.idCompressor.tryRecompress(cursor.value);
|
|
79
79
|
if (sessionSpaceCompressedId !== undefined) {
|
|
80
|
-
|
|
80
|
+
const opSpaceId = context.idCompressor.normalizeToOpSpace(sessionSpaceCompressedId);
|
|
81
|
+
// Summaries can only contain finalized op-space ids unless they also include the originator's session id somewhere.
|
|
82
|
+
// This is not the case for forest summaries at the time of writing, so non-finalized ids are instead written using
|
|
83
|
+
// their long form (by falling through to the original cursor value).
|
|
84
|
+
// A scenario where such ids can appear in the summary is in the attach summary of a tree being attached to an already-attached container.
|
|
85
|
+
// TODO: isFinalId should probably be exported from id-compressor and that could be used to do the narrowing here.
|
|
86
|
+
if (!context.isSummary || opSpaceId >= 0) {
|
|
87
|
+
return opSpaceId;
|
|
88
|
+
}
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
}
|
|
@@ -54,6 +54,8 @@ export function schemaCompressedEncodeV1(
|
|
|
54
54
|
policy: SchemaPolicy,
|
|
55
55
|
fieldBatch: FieldBatch,
|
|
56
56
|
idCompressor: IIdCompressor,
|
|
57
|
+
_incrementalEncoder: IncrementalEncoder | undefined,
|
|
58
|
+
isSummary: boolean,
|
|
57
59
|
): EncodedFieldBatchV1 {
|
|
58
60
|
const encoded: EncodedFieldBatchV1OrV2 = schemaCompressedEncode(
|
|
59
61
|
schema,
|
|
@@ -62,6 +64,7 @@ export function schemaCompressedEncodeV1(
|
|
|
62
64
|
idCompressor,
|
|
63
65
|
undefined /* incrementalEncoder */,
|
|
64
66
|
brand(FieldBatchFormatVersion.v1),
|
|
67
|
+
isSummary,
|
|
65
68
|
);
|
|
66
69
|
// Since incrementalEncoder was not provided, no V2 features should be used, and this cast should be safe.
|
|
67
70
|
return encoded as EncodedFieldBatchV1;
|
|
@@ -78,6 +81,7 @@ export function schemaCompressedEncodeV2(
|
|
|
78
81
|
fieldBatch: FieldBatch,
|
|
79
82
|
idCompressor: IIdCompressor,
|
|
80
83
|
incrementalEncoder: IncrementalEncoder | undefined,
|
|
84
|
+
isSummary: boolean,
|
|
81
85
|
): EncodedFieldBatchV2 {
|
|
82
86
|
return schemaCompressedEncode(
|
|
83
87
|
schema,
|
|
@@ -86,6 +90,7 @@ export function schemaCompressedEncodeV2(
|
|
|
86
90
|
idCompressor,
|
|
87
91
|
incrementalEncoder,
|
|
88
92
|
brand(FieldBatchFormatVersion.v2),
|
|
93
|
+
isSummary,
|
|
89
94
|
);
|
|
90
95
|
}
|
|
91
96
|
|
|
@@ -106,10 +111,11 @@ function schemaCompressedEncode(
|
|
|
106
111
|
idCompressor: IIdCompressor,
|
|
107
112
|
incrementalEncoder: IncrementalEncoder | undefined,
|
|
108
113
|
version: FieldBatchFormatVersion,
|
|
114
|
+
isSummary: boolean,
|
|
109
115
|
): EncodedFieldBatchV1OrV2 {
|
|
110
116
|
return compressedEncode(
|
|
111
117
|
fieldBatch,
|
|
112
|
-
buildContext(schema, policy, idCompressor, incrementalEncoder, version),
|
|
118
|
+
buildContext(schema, policy, idCompressor, incrementalEncoder, version, isSummary),
|
|
113
119
|
);
|
|
114
120
|
}
|
|
115
121
|
|
|
@@ -119,6 +125,7 @@ export function buildContext(
|
|
|
119
125
|
idCompressor: IIdCompressor,
|
|
120
126
|
incrementalEncoder: IncrementalEncoder | undefined,
|
|
121
127
|
version: FieldBatchFormatVersion,
|
|
128
|
+
isSummary: boolean,
|
|
122
129
|
): EncoderContext {
|
|
123
130
|
const context: EncoderContext = new EncoderContext(
|
|
124
131
|
(fieldBuilder: FieldEncodeBuilder, schemaName: TreeNodeSchemaIdentifier) =>
|
|
@@ -129,6 +136,7 @@ export function buildContext(
|
|
|
129
136
|
idCompressor,
|
|
130
137
|
incrementalEncoder,
|
|
131
138
|
version,
|
|
139
|
+
isSummary,
|
|
132
140
|
);
|
|
133
141
|
return context;
|
|
134
142
|
}
|
|
@@ -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
|
}
|