From d8ca2b884d8bbde7ba74f4e6d8f5b88fead59985 Mon Sep 17 00:00:00 2001 From: Yann Achard Date: Wed, 5 Mar 2025 16:11:39 -0800 Subject: [PATCH 1/7] refactor(tree): use TreeChunk up to Delta --- .../tree/src/core/forest/editableForest.ts | 6 +- packages/dds/tree/src/core/index.ts | 1 - packages/dds/tree/src/core/tree/anchorSet.ts | 4 +- packages/dds/tree/src/core/tree/delta.ts | 31 +---- packages/dds/tree/src/core/tree/deltaUtil.ts | 10 +- packages/dds/tree/src/core/tree/index.ts | 1 - packages/dds/tree/src/core/tree/visitDelta.ts | 27 ++-- .../dds/tree/src/core/tree/visitorUtils.ts | 9 +- .../chunked-forest/chunkedForest.ts | 3 +- .../tree/src/feature-libraries/deltaUtils.ts | 2 +- .../forest-summary/forestSummarizer.ts | 14 +- .../indexing/anchorTreeIndex.ts | 4 +- .../modular-schema/modularChangeFamily.ts | 5 +- .../object-forest/objectForest.ts | 3 +- .../tree/src/shared-tree/independentView.ts | 22 +-- .../src/test/domains/json/jsonCursor.bench.ts | 5 +- .../defaultChangeFamily.spec.ts | 3 +- .../test/feature-libraries/deltaUtils.spec.ts | 21 +-- .../modularChangeFamily.spec.ts | 54 ++++---- .../modularChangeFamilyIntegration.spec.ts | 23 +--- .../feature-libraries/objectForest.spec.ts | 12 +- packages/dds/tree/src/test/forestTestSuite.ts | 130 ++++++++---------- .../sharedTreeChangeEnricher.spec.ts | 9 +- .../dds/tree/src/test/simple-tree/utils.ts | 6 +- .../dds/tree/src/test/tree/anchorSet.spec.ts | 8 +- .../dds/tree/src/test/tree/visitDelta.spec.ts | 92 +++++++------ packages/dds/tree/src/test/utils.ts | 37 +++-- 27 files changed, 247 insertions(+), 295 deletions(-) diff --git a/packages/dds/tree/src/core/forest/editableForest.ts b/packages/dds/tree/src/core/forest/editableForest.ts index 8f93bc591bb5..766d939ba177 100644 --- a/packages/dds/tree/src/core/forest/editableForest.ts +++ b/packages/dds/tree/src/core/forest/editableForest.ts @@ -21,6 +21,7 @@ import { } from "../tree/index.js"; import type { IForestSubscription, ITreeSubscriptionCursor } from "./forest.js"; +import { chunkTree, defaultChunkPolicy } from "../../feature-libraries/index.js"; /** * Editing APIs. @@ -51,13 +52,14 @@ export interface IEditableForest extends IForestSubscription { */ export function initializeForest( forest: IEditableForest, - content: readonly ITreeCursorSynchronous[], + content: ITreeCursorSynchronous, revisionTagCodec: RevisionTagCodec, idCompressor: IIdCompressor, visitAnchors = false, ): void { assert(forest.isEmpty, 0x747 /* forest must be empty */); - const delta: DeltaRoot = deltaForRootInitialization(content); + const chunk = chunkTree(content, { idCompressor, policy: defaultChunkPolicy }); + const delta: DeltaRoot = deltaForRootInitialization(chunk); let visitor = forest.acquireVisitor(); if (visitAnchors) { assert(forest.anchors.isEmpty(), 0x9b7 /* anchor set must be empty */); diff --git a/packages/dds/tree/src/core/index.ts b/packages/dds/tree/src/core/index.ts index 6db204146c48..02127647dfc2 100644 --- a/packages/dds/tree/src/core/index.ts +++ b/packages/dds/tree/src/core/index.ts @@ -83,7 +83,6 @@ export { getDetachedFieldContainingPath, aboveRootPlaceholder, type DeltaRoot, - type DeltaProtoNode, type DeltaMark, type DeltaDetachedNodeId, type DeltaFieldMap, diff --git a/packages/dds/tree/src/core/tree/anchorSet.ts b/packages/dds/tree/src/core/tree/anchorSet.ts index 785ed84daf0b..402bc2004f65 100644 --- a/packages/dds/tree/src/core/tree/anchorSet.ts +++ b/packages/dds/tree/src/core/tree/anchorSet.ts @@ -23,10 +23,10 @@ import { } from "../../util/index.js"; import type { FieldKey } from "../schema-stored/index.js"; -import type * as Delta from "./delta.js"; import type { PlaceIndex, Range, UpPath } from "./pathTree.js"; import { EmptyKey } from "./types.js"; import type { DeltaVisitor } from "./visitDelta.js"; +import type { ITreeCursorSynchronous } from "./cursor.js"; /** * A way to refer to a particular tree location within an {@link AnchorSet}. @@ -864,7 +864,7 @@ export class AnchorSet implements AnchorLocator { count, ); }, - create(content: Delta.ProtoNodes, destination: FieldKey): void { + create(content: ITreeCursorSynchronous[], destination: FieldKey): void { // Nothing to do since content can only be created in a new detached field, // which cannot contain any anchors. }, diff --git a/packages/dds/tree/src/core/tree/delta.ts b/packages/dds/tree/src/core/tree/delta.ts index fd4382929a98..9bce13f29533 100644 --- a/packages/dds/tree/src/core/tree/delta.ts +++ b/packages/dds/tree/src/core/tree/delta.ts @@ -5,8 +5,7 @@ import type { RevisionTag } from "../rebase/index.js"; import type { FieldKey } from "../schema-stored/index.js"; - -import type { ITreeCursorSynchronous } from "./cursor.js"; +import type { TreeChunk } from "./chunk.js"; /** * This format describes changes that must be applied to a forest in order to update it. @@ -70,7 +69,7 @@ import type { ITreeCursorSynchronous } from "./cursor.js"; * Represents the change made to a document. * Immutable, therefore safe to retain for async processing. */ -export interface Root { +export interface Root { /** * Changes to apply to the root fields. */ @@ -83,7 +82,7 @@ export interface Root { * For example, if one wishes to build a tree which is being renamed from ID A to ID B, * then the build should be listed under ID A. */ - readonly build?: readonly DetachedNodeBuild[]; + readonly build?: readonly DetachedNodeBuild[]; /** * New detached nodes to be destroyed. * The ordering has no significance. @@ -97,7 +96,7 @@ export interface Root { * Refreshers for detached nodes that may need to be recreated. * The ordering has no significance. */ - readonly refreshers?: readonly DetachedNodeBuild[]; + readonly refreshers?: readonly DetachedNodeBuild[]; /** * Changes to apply to detached nodes. * The ordering has no significance. @@ -116,26 +115,10 @@ export interface Root { readonly rename?: readonly DetachedNodeRename[]; } -/** - * The default representation for inserted content. - * - * TODO: - * Ownership and lifetime of data referenced by this cursor is unclear, - * so it is a poor abstraction for this use-case which needs to hold onto the data in a non-exclusive (readonly) way. - * Cursors can be one supported way to input data, but aren't a good storage format. - */ -export type ProtoNode = ITreeCursorSynchronous; - /** * The default representation a chunk (sub-sequence) of inserted content. - * - * TODO: - * See issue TODO with ProtoNode. - * Additionally, Cursors support sequences, so if using cursors, there are better ways to handle this than an array of cursors, - * like using a cursor over all the content (starting in fields mode). - * Long term something like TreeChunk should probably be used here. */ -export type ProtoNodes = readonly ProtoNode[]; +export type ProtoNodes = TreeChunk; /** * Represents a change being made to a part of the document tree. @@ -192,9 +175,9 @@ export interface DetachedNodeChanges { * Tree creation is idempotent: if a tree with the same ID already exists, * then this build is ignored in favor of the existing tree. */ -export interface DetachedNodeBuild { +export interface DetachedNodeBuild { readonly id: DetachedNodeId; - readonly trees: readonly TTree[]; + readonly trees: TTrees; } /** diff --git a/packages/dds/tree/src/core/tree/deltaUtil.ts b/packages/dds/tree/src/core/tree/deltaUtil.ts index 97eeb2660b7f..ccf5124187c0 100644 --- a/packages/dds/tree/src/core/tree/deltaUtil.ts +++ b/packages/dds/tree/src/core/tree/deltaUtil.ts @@ -5,12 +5,12 @@ import type { Mutable } from "../../util/index.js"; import type { FieldKey } from "../schema-stored/index.js"; +import type { TreeChunk } from "./chunk.js"; -import type { ITreeCursorSynchronous } from "./cursor.js"; import type { DetachedNodeId, FieldChanges, Mark, Root } from "./delta.js"; import { rootFieldKey } from "./types.js"; -export const emptyDelta: Root = {}; +export const emptyDelta: Root = {}; export function isAttachMark(mark: Mark): boolean { return mark.attach !== undefined && mark.detach === undefined; @@ -24,15 +24,15 @@ export function isReplaceMark(mark: Mark): boolean { return mark.detach !== undefined && mark.attach !== undefined; } -export function deltaForRootInitialization(content: readonly ITreeCursorSynchronous[]): Root { - if (content.length === 0) { +export function deltaForRootInitialization(content: TreeChunk): Root { + if (content.topLevelLength === 0) { return emptyDelta; } const buildId = { minor: 0 }; const delta: Root = { build: [{ id: buildId, trees: content }], fields: new Map([ - [rootFieldKey, [{ count: content.length, attach: buildId }]], + [rootFieldKey, [{ count: content.topLevelLength, attach: buildId }]], ]), }; return delta; diff --git a/packages/dds/tree/src/core/tree/index.ts b/packages/dds/tree/src/core/tree/index.ts index 548434222918..67a69ab3e50d 100644 --- a/packages/dds/tree/src/core/tree/index.ts +++ b/packages/dds/tree/src/core/tree/index.ts @@ -33,7 +33,6 @@ export { export type { ProtoNodes, Root as DeltaRoot, - ProtoNode as DeltaProtoNode, Mark as DeltaMark, DetachedNodeId as DeltaDetachedNodeId, FieldMap as DeltaFieldMap, diff --git a/packages/dds/tree/src/core/tree/visitDelta.ts b/packages/dds/tree/src/core/tree/visitDelta.ts index 50e8d7ac2749..65ac76d6c7de 100644 --- a/packages/dds/tree/src/core/tree/visitDelta.ts +++ b/packages/dds/tree/src/core/tree/visitDelta.ts @@ -8,12 +8,8 @@ import { assert } from "@fluidframework/core-utils/internal"; import { type NestedMap, setInNestedMap, tryGetFromNestedMap } from "../../util/index.js"; import type { FieldKey } from "../schema-stored/index.js"; -import type { ITreeCursorSynchronous } from "./cursor.js"; -// eslint-disable-next-line import/no-duplicates +import { mapCursorField, type ITreeCursorSynchronous } from "./cursor.js"; import type * as Delta from "./delta.js"; -// Since ProtoNodes is reexported, import it directly to avoid forcing Delta to be reexported. -// eslint-disable-next-line import/no-duplicates -import type { ProtoNodes } from "./delta.js"; import { areDetachedNodeIdsEqual, isAttachMark, @@ -24,7 +20,7 @@ import { import type { DetachedFieldIndex } from "./detachedFieldIndex.js"; import type { ForestRootId, Major, Minor } from "./detachedFieldIndexTypes.js"; import type { NodeIndex, PlaceIndex, Range } from "./pathTree.js"; -import type { RevisionTag } from "../index.js"; +import type { RevisionTag, TreeChunk } from "../index.js"; /** * Implementation notes: @@ -89,9 +85,10 @@ export function visitDelta( const rootDestructions: Delta.DetachedNodeDestruction[] = []; const refreshers: NestedMap = new Map(); delta.refreshers?.forEach(({ id: { major, minor }, trees }) => { - for (let i = 0; i < trees.length; i += 1) { + const treeCursors = nodeCursorsFromChunk(trees); + for (let i = 0; i < trees.topLevelLength; i += 1) { const offsettedId = minor + i; - setInNestedMap(refreshers, major, offsettedId, trees[i]); + setInNestedMap(refreshers, major, offsettedId, treeCursors[i]); } }); const detachConfig: PassConfig = { @@ -266,7 +263,7 @@ export interface DeltaVisitor { * @param destination - The key for a new detached field. * A field with this key must not already exist. */ - create(content: ProtoNodes, destination: FieldKey): void; + create(content: ITreeCursorSynchronous[], destination: FieldKey): void; /** * Recursively destroys the given detached field and all of the nodes within it. * @param detachedField - The key for the detached field to destroy. @@ -476,7 +473,13 @@ function processBuilds( ): void { if (builds !== undefined) { for (const { id, trees } of builds) { - buildTrees(id, trees, config.detachedFieldIndex, config.latestRevision, visitor); + buildTrees( + id, + nodeCursorsFromChunk(trees), + config.detachedFieldIndex, + config.latestRevision, + visitor, + ); } } } @@ -592,3 +595,7 @@ function attachPass( } } } + +function nodeCursorsFromChunk(trees: TreeChunk): ITreeCursorSynchronous[] { + return mapCursorField(trees.cursor(), (c) => c.fork()); +} diff --git a/packages/dds/tree/src/core/tree/visitorUtils.ts b/packages/dds/tree/src/core/tree/visitorUtils.ts index d9c6b15b55aa..5c1757bbbc30 100644 --- a/packages/dds/tree/src/core/tree/visitorUtils.ts +++ b/packages/dds/tree/src/core/tree/visitorUtils.ts @@ -10,12 +10,13 @@ import { type IdAllocator, idAllocatorFromMaxId } from "../../util/index.js"; import type { RevisionTag, RevisionTagCodec } from "../rebase/index.js"; import type { FieldKey } from "../schema-stored/index.js"; -import type { ProtoNodes, Root } from "./delta.js"; +import type { Root } from "./delta.js"; import { DetachedFieldIndex } from "./detachedFieldIndex.js"; import type { ForestRootId } from "./detachedFieldIndexTypes.js"; import type { NodeIndex, PlaceIndex, Range } from "./pathTree.js"; import { type DeltaVisitor, visitDelta } from "./visitDelta.js"; import type { IIdCompressor } from "@fluidframework/id-compressor"; +import type { ITreeCursorSynchronous } from "./cursor.js"; export function makeDetachedFieldIndex( prefix: string = "Temp", @@ -120,7 +121,7 @@ export interface AnnouncedVisitor extends DeltaVisitor { /** * A hook that is called after all nodes have been created. */ - afterCreate(content: ProtoNodes, destination: FieldKey): void; + afterCreate(content: ITreeCursorSynchronous[], destination: FieldKey): void; beforeDestroy(field: FieldKey, count: number): void; beforeAttach(source: FieldKey, count: number, destination: PlaceIndex): void; afterAttach(source: FieldKey, destination: Range): void; @@ -140,8 +141,8 @@ export interface AnnouncedVisitor extends DeltaVisitor { */ export function createAnnouncedVisitor(visitorFunctions: { free?: () => void; - create?: (content: ProtoNodes, destination: FieldKey) => void; - afterCreate?: (content: ProtoNodes, destination: FieldKey) => void; + create?: (content: ITreeCursorSynchronous[], destination: FieldKey) => void; + afterCreate?: (content: ITreeCursorSynchronous[], destination: FieldKey) => void; beforeDestroy?: (field: FieldKey, count: number) => void; destroy?: (detachedField: FieldKey, count: number) => void; beforeAttach?: (source: FieldKey, count: number, destination: PlaceIndex) => void; diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts index 535a36581fdb..81f509c0d6d1 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts @@ -21,7 +21,6 @@ import { type ITreeSubscriptionCursor, ITreeSubscriptionCursorState, type PlaceIndex, - type ProtoNodes, type Range, TreeNavigationResult, type TreeStoredSchemaSubscription, @@ -136,7 +135,7 @@ export class ChunkedForest implements IEditableForest { this.forest.#events.emit("beforeChange"); this.forest.roots.fields.delete(detachedField); }, - create(content: ProtoNodes, destination: FieldKey): void { + create(content: ITreeCursorSynchronous[], destination: FieldKey): void { this.forest.#events.emit("beforeChange"); const chunks: TreeChunk[] = content.map((c) => chunkTree(c, { diff --git a/packages/dds/tree/src/feature-libraries/deltaUtils.ts b/packages/dds/tree/src/feature-libraries/deltaUtils.ts index 090d45c4d372..b897b79e8f0f 100644 --- a/packages/dds/tree/src/feature-libraries/deltaUtils.ts +++ b/packages/dds/tree/src/feature-libraries/deltaUtils.ts @@ -36,7 +36,7 @@ export function mapRootChanges( if (root.build !== undefined) { out.build = root.build.map(({ id, trees }) => ({ id, - trees: trees.map(func), + trees: func(trees), })); } if (root.global !== undefined) { diff --git a/packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts b/packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts index eff53991d496..fef11e94674a 100644 --- a/packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts +++ b/packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts @@ -25,7 +25,6 @@ import { applyDelta, forEachField, makeDetachedFieldIndex, - mapCursorField, } from "../../core/index.js"; import type { Summarizable, @@ -34,7 +33,7 @@ import type { } from "../../shared-tree-core/index.js"; import { idAllocatorFromMaxId } from "../../util/index.js"; // eslint-disable-next-line import/no-internal-modules -import { chunkField, defaultChunkPolicy } from "../chunked-forest/chunkTree.js"; +import { chunkFieldSingle, defaultChunkPolicy } from "../chunked-forest/chunkTree.js"; import type { FieldBatchCodec, FieldBatchEncodingContext } from "../chunked-forest/index.js"; import { type ForestCodec, makeForestSummarizerCodec } from "./codec.js"; @@ -127,19 +126,16 @@ export class ForestSummarizer implements Summarizable { const fieldChanges: [FieldKey, DeltaFieldChanges][] = []; const build: DeltaDetachedNodeBuild[] = []; for (const [fieldKey, field] of fields) { - const chunked = chunkField(field, { + const chunked = chunkFieldSingle(field, { policy: defaultChunkPolicy, idCompressor: this.idCompressor, }); - const nodeCursors = chunked.flatMap((chunk) => - mapCursorField(chunk.cursor(), (cursor) => cursor.fork()), - ); - const buildId = { minor: allocator.allocate(nodeCursors.length) }; + const buildId = { minor: allocator.allocate(chunked.topLevelLength) }; build.push({ id: buildId, - trees: nodeCursors, + trees: chunked, }); - fieldChanges.push([fieldKey, [{ count: nodeCursors.length, attach: buildId }]]); + fieldChanges.push([fieldKey, [{ count: chunked.topLevelLength, attach: buildId }]]); } assert(this.forest.isEmpty, 0x797 /* forest must be empty */); diff --git a/packages/dds/tree/src/feature-libraries/indexing/anchorTreeIndex.ts b/packages/dds/tree/src/feature-libraries/indexing/anchorTreeIndex.ts index 7d1efb3ad471..9297bbf1714b 100644 --- a/packages/dds/tree/src/feature-libraries/indexing/anchorTreeIndex.ts +++ b/packages/dds/tree/src/feature-libraries/indexing/anchorTreeIndex.ts @@ -19,10 +19,10 @@ import { CursorLocationType, rootField, type UpPath, - type ProtoNodes, keyAsDetachedField, compareUpPaths, TreeNavigationResult, + type ITreeCursorSynchronous, } from "../../core/index.js"; import type { TreeIndex, TreeIndexKey, TreeIndexNodes } from "./types.js"; import { TreeStatus } from "../flex-tree/index.js"; @@ -123,7 +123,7 @@ export class AnchorTreeIndex return createAnnouncedVisitor({ // nodes (and their entire subtrees) are added to the index as soon as they are created - afterCreate: (content: ProtoNodes, destination: FieldKey) => { + afterCreate: (content: ITreeCursorSynchronous[], destination: FieldKey) => { const detachedCursor = this.forest.allocateCursor(); assert( this.forest.tryMoveCursorToField( diff --git a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts index 08f0073b2f46..c1061799684a 100644 --- a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts +++ b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts @@ -2044,12 +2044,9 @@ function copyDetachedNodes( const copiedDetachedNodes: DeltaDetachedNodeBuild[] = []; for (const [[major, minor], chunk] of detachedNodes.entries()) { if (chunk.topLevelLength > 0) { - const trees = mapCursorField(chunk.cursor(), (c) => - cursorForMapTreeNode(mapTreeFromCursor(c)), - ); copiedDetachedNodes.push({ id: makeDetachedNodeId(major, minor), - trees, + trees: chunk, }); } } diff --git a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts index 6636eac942a9..96000c284723 100644 --- a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts +++ b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts @@ -26,7 +26,6 @@ import { type MapTree, type PathRootPrefix, type PlaceIndex, - type ProtoNodes, type Range, TreeNavigationResult, type TreeNodeSchemaIdentifier, @@ -163,7 +162,7 @@ export class ObjectForest implements IEditableForest { preEdit(); this.forest.delete(detachedField); } - public create(content: ProtoNodes, destination: FieldKey): void { + public create(content: ITreeCursorSynchronous[], destination: FieldKey): void { preEdit(); this.forest.add(content, destination); this.forest.#events.emit("afterRootFieldCreated", destination); diff --git a/packages/dds/tree/src/shared-tree/independentView.ts b/packages/dds/tree/src/shared-tree/independentView.ts index 0ef5c6fef873..233627046892 100644 --- a/packages/dds/tree/src/shared-tree/independentView.ts +++ b/packages/dds/tree/src/shared-tree/independentView.ts @@ -116,9 +116,7 @@ export function independentInitializedView("foo"); const detachId: DeltaDetachedNodeId = { minor: 43 }; @@ -44,7 +37,7 @@ describe("DeltaUtils", () => { ], ]); const input: DeltaRoot = { - build: [{ id: detachId, trees: [nodeXCursor] }], + build: [{ id: detachId, trees: nodeX }], fields: new Map([ [ fooField, @@ -59,7 +52,7 @@ describe("DeltaUtils", () => { global: [{ id: detachId, fields: nestedCursorInsert }], }; deepFreeze(input); - const actual = mapRootChanges(input, mapTreeFromCursor); + const actual = mapRootChanges(input, chunkToMapTreeField); const nestedMapTreeInsert = new Map([ [ fooField, @@ -72,8 +65,8 @@ describe("DeltaUtils", () => { ], ], ]); - const expected: DeltaRoot = { - build: [{ id: detachId, trees: [nodeX] }], + const expected: DeltaRoot = { + build: [{ id: detachId, trees: chunkToMapTreeField(nodeX) }], fields: new Map([ [ fooField, diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangeFamily.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangeFamily.spec.ts index 52aaf35f7357..16c8f6cb1d8a 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/modularChangeFamily.spec.ts @@ -64,6 +64,7 @@ import { import { type EncodingTestData, assertDeltaEqual, + chunkFromJsonField, makeEncodingTestSuite, mintRevisionTag, testChangeReceiver, @@ -441,9 +442,8 @@ const rootChangeWithoutNodeFieldChanges: ModularChangeset = family.compose([ makeAnonChange(buildExistsConstraint(pathA0)), ]); -const node1 = singleJsonCursor(1); -const objectNode = singleJsonCursor({}); -const node1Chunk = treeChunkFromCursor(node1); +const objectNode = chunkFromJsonField([{}]); +const node1Chunk = chunkFromJsonField([1]); const nodesChunk = chunkFieldSingle(fieldJsonCursor([{}, {}]), { policy: defaultChunkPolicy, idCompressor: testIdCompressor, @@ -753,8 +753,8 @@ describe("ModularChangeFamily", () => { { ...Change.empty(), builds: newTupleBTree([ - [[undefined, brand(0)], treeChunkFromCursor(node1)], - [[tag3, brand(0)], treeChunkFromCursor(node1)], + [[undefined, brand(0)], node1Chunk], + [[tag3, brand(0)], node1Chunk], ]), destroys: newTupleBTree([ [[undefined, brand(1)], 1], @@ -768,8 +768,8 @@ describe("ModularChangeFamily", () => { { ...Change.empty(), builds: newTupleBTree([ - [[undefined, brand(2)], treeChunkFromCursor(node1)], - [[tag3, brand(2)], treeChunkFromCursor(node1)], + [[undefined, brand(2)], node1Chunk], + [[tag3, brand(2)], node1Chunk], ]), destroys: newTupleBTree([ [[undefined, brand(3)], 1], @@ -786,17 +786,17 @@ describe("ModularChangeFamily", () => { const expected: ModularChangeset = { ...Change.empty(), builds: newTupleBTree([ - [[tag1 as RevisionTag | undefined, brand(0)], treeChunkFromCursor(node1)], - [[tag2, brand(2)], treeChunkFromCursor(node1)], + [[tag1 as RevisionTag | undefined, brand(0)], node1Chunk], + [[tag2, brand(2)], node1Chunk], [ [ tag3, brand(0), ], - treeChunkFromCursor(node1), + node1Chunk, ], - [[tag3, brand(2)], treeChunkFromCursor(node1)], + [[tag3, brand(2)], node1Chunk], ]), destroys: newTupleBTree([ [[tag1 as RevisionTag | undefined, brand(1)], 1], @@ -815,7 +815,7 @@ describe("ModularChangeFamily", () => { { ...Change.empty(), refreshers: newTupleBTree([ - [[tag3 as RevisionTag | undefined, brand(0)], treeChunkFromCursor(node1)], + [[tag3 as RevisionTag | undefined, brand(0)], node1Chunk], ]), }, tag1, @@ -825,8 +825,8 @@ describe("ModularChangeFamily", () => { { ...Change.empty(), refreshers: newTupleBTree([ - [[undefined, brand(2)], treeChunkFromCursor(node1)], - [[tag3, brand(2)], treeChunkFromCursor(node1)], + [[undefined, brand(2)], node1Chunk], + [[tag3, brand(2)], node1Chunk], ]), revisions: [{ revision: tag2 }], }, @@ -840,9 +840,9 @@ describe("ModularChangeFamily", () => { const expected: ModularChangeset = { ...Change.empty(), refreshers: newTupleBTree([ - [[undefined, brand(2)], treeChunkFromCursor(node1)], - [[tag3, brand(0)], treeChunkFromCursor(node1)], - [[tag3, brand(2)], treeChunkFromCursor(node1)], + [[undefined, brand(2)], node1Chunk], + [[tag3, brand(0)], node1Chunk], + [[tag3, brand(2)], node1Chunk], ]), revisions: [{ revision: tag1 }, { revision: tag2 }], }; @@ -855,7 +855,7 @@ describe("ModularChangeFamily", () => { { ...Change.empty(), refreshers: newTupleBTree([ - [[tag3 as RevisionTag | undefined, brand(0)], treeChunkFromCursor(node1)], + [[tag3 as RevisionTag | undefined, brand(0)], node1Chunk], ]), }, tag1, @@ -865,7 +865,7 @@ describe("ModularChangeFamily", () => { { ...Change.empty(), refreshers: newTupleBTree([ - [[tag3 as RevisionTag | undefined, brand(0)], treeChunkFromCursor(objectNode)], + [[tag3 as RevisionTag | undefined, brand(0)], objectNode], ]), revisions: [{ revision: tag2 }], }, @@ -878,9 +878,7 @@ describe("ModularChangeFamily", () => { const expected: ModularChangeset = { ...Change.empty(), - refreshers: newTupleBTree([ - [[tag3 as RevisionTag | undefined, brand(0)], treeChunkFromCursor(node1)], - ]), + refreshers: newTupleBTree([[[tag3 as RevisionTag | undefined, brand(0)], node1Chunk]]), revisions: [{ revision: tag1 }, { revision: tag2 }], }; @@ -1088,9 +1086,9 @@ describe("ModularChangeFamily", () => { const expectedDelta: DeltaRoot = { build: [ - { id: { major: tag1, minor: 1 }, trees: [node1] }, - { id: { major: tag2, minor: 2 }, trees: [node1] }, - { id: { major: tag2, minor: 3 }, trees: [objectNode, objectNode] }, + { id: { major: tag1, minor: 1 }, trees: node1Chunk }, + { id: { major: tag2, minor: 2 }, trees: node1Chunk }, + { id: { major: tag2, minor: 3 }, trees: nodesChunk }, ], }; @@ -1138,9 +1136,9 @@ describe("ModularChangeFamily", () => { const expectedDelta: DeltaRoot = { refreshers: [ - { id: { major: tag1, minor: 1 }, trees: [node1] }, - { id: { major: tag2, minor: 2 }, trees: [node1] }, - { id: { major: tag2, minor: 3 }, trees: [objectNode, objectNode] }, + { id: { major: tag1, minor: 1 }, trees: node1Chunk }, + { id: { major: tag2, minor: 2 }, trees: node1Chunk }, + { id: { major: tag2, minor: 3 }, trees: nodesChunk }, ], }; diff --git a/packages/dds/tree/src/test/feature-libraries/modularChangeFamilyIntegration.spec.ts b/packages/dds/tree/src/test/feature-libraries/modularChangeFamilyIntegration.spec.ts index 6387a3a32d2c..0a661c328f03 100644 --- a/packages/dds/tree/src/test/feature-libraries/modularChangeFamilyIntegration.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modularChangeFamilyIntegration.spec.ts @@ -25,7 +25,6 @@ import { DefaultEditBuilder, type FieldKindWithEditor, type ModularChangeset, - cursorForJsonableTreeNode, type SequenceField as SF, type EditDescription, genericFieldKind, @@ -43,6 +42,7 @@ import { } from "../../util/index.js"; import { assertDeltaEqual, + chunkFromJsonField, defaultRevisionMetadataFromChanges, failCodecFamily, mintRevisionTag, @@ -60,7 +60,6 @@ import { MarkMaker } from "./sequence-field/testEdits.js"; import { assertEqual, Change, removeAliases } from "./modular-schema/modularChangesetUtil.js"; // eslint-disable-next-line import/no-internal-modules import { newGenericChangeset } from "../../feature-libraries/modular-schema/genericFieldKindTypes.js"; -import { numberSchema } from "../../simple-tree/index.js"; const fieldKinds: ReadonlyMap = new Map([ [sequence.identifier, sequence], @@ -602,24 +601,20 @@ describe("ModularChangeFamily integration", () => { 0, ); - const newValue = "new value"; - const newNode = cursorForJsonableTreeNode({ - type: brand(numberSchema.identifier), - value: newValue, - }); + const newNode = chunkFromJsonField(["new value"]); editor .sequenceField({ parent: { parent: undefined, parentField: fieldB, parentIndex: 0 }, field: fieldC, }) - .insert(0, newNode); + .insert(0, newNode.cursor()); const [move, insert] = getChanges(); const composed = family.compose([makeAnonChange(move), makeAnonChange(insert)]); const tagForCompare = mintRevisionTag(); const taggedComposed = tagChangeInline(composed, tagForCompare); const expected: DeltaRoot = { - build: [{ id: { minor: 2, major: tagForCompare }, trees: [newNode] }], + build: [{ id: { minor: 2, major: tagForCompare }, trees: newNode }], fields: new Map([ [ fieldA, @@ -653,17 +648,13 @@ describe("ModularChangeFamily integration", () => { 0, ); - const newValue = "new value"; - const newNode = cursorForJsonableTreeNode({ - type: brand(numberSchema.identifier), - value: newValue, - }); + const newNode = chunkFromJsonField(["new value"]); editor .sequenceField({ parent: { parent: undefined, parentField: fieldB, parentIndex: 0 }, field: fieldC, }) - .insert(0, newNode); + .insert(0, newNode.cursor()); const [move, insert] = getChanges(); const moveTagged = tagChangeInline(move, tag1); @@ -682,7 +673,7 @@ describe("ModularChangeFamily integration", () => { build: [ { id: { major: tag2, minor: 2 }, - trees: [newNode], + trees: newNode, }, ], fields: new Map([ diff --git a/packages/dds/tree/src/test/feature-libraries/objectForest.spec.ts b/packages/dds/tree/src/test/feature-libraries/objectForest.spec.ts index 66940010fe5e..69531e7316da 100644 --- a/packages/dds/tree/src/test/feature-libraries/objectForest.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/objectForest.spec.ts @@ -13,7 +13,7 @@ import { moveToDetachedField, rootFieldKey, } from "../../core/index.js"; -import { singleJsonCursor } from "../json/index.js"; +import { fieldJsonCursor } from "../json/index.js"; import { cursorForMapTreeNode } from "../../feature-libraries/index.js"; // Allow importing from this specific file which is being tested: /* eslint-disable-next-line import/no-internal-modules */ @@ -38,7 +38,7 @@ describe("object-forest", () => { const forest = buildForest(); initializeForest( forest, - [singleJsonCursor(content)], + fieldJsonCursor([content]), testRevisionTagCodec, testIdCompressor, ); @@ -60,7 +60,7 @@ describe("object-forest", () => { const forest = buildForest(); initializeForest( forest, - [singleJsonCursor(content)], + fieldJsonCursor([content]), testRevisionTagCodec, testIdCompressor, ); @@ -82,7 +82,7 @@ describe("object-forest", () => { const forest = buildForest(); initializeForest( forest, - [singleJsonCursor(content)], + fieldJsonCursor([content]), testRevisionTagCodec, testIdCompressor, ); @@ -105,7 +105,7 @@ describe("object-forest", () => { const forest = buildForest(); initializeForest( forest, - [singleJsonCursor([1, 2])], + fieldJsonCursor([[1, 2]]), testRevisionTagCodec, testIdCompressor, ); @@ -118,7 +118,7 @@ describe("object-forest", () => { const forest = buildForest(); initializeForest( forest, - [singleJsonCursor(content)], + fieldJsonCursor([content]), testRevisionTagCodec, testIdCompressor, ); diff --git a/packages/dds/tree/src/test/forestTestSuite.ts b/packages/dds/tree/src/test/forestTestSuite.ts index 23320d5da932..ad3269bb7e10 100644 --- a/packages/dds/tree/src/test/forestTestSuite.ts +++ b/packages/dds/tree/src/test/forestTestSuite.ts @@ -32,8 +32,11 @@ import { } from "../core/index.js"; import { typeboxValidator } from "../external-utilities/index.js"; import { - cursorForJsonableTreeNode, + chunkTree, + cursorForJsonableTreeField, + defaultChunkPolicy, jsonableTreeFromCursor, + type TreeChunk, } from "../feature-libraries/index.js"; import { type IdAllocator, @@ -45,6 +48,8 @@ import { import { testGeneralPurposeTreeCursor, testTreeSchema } from "./cursorTestSuite.js"; import { applyTestDelta, + chunkFromJsonableField, + chunkFromJsonField, expectEqualFieldPaths, expectEqualPaths, testIdCompressor, @@ -52,14 +57,13 @@ import { } from "./utils.js"; import { booleanSchema, - cursorFromInsertable, numberSchema, SchemaFactory, stringSchema, toStoredSchema, } from "../simple-tree/index.js"; import { jsonSequenceRootSchema } from "./sequenceRootUtils.js"; -import { cursorToJsonObject, singleJsonCursor } from "./json/index.js"; +import { cursorToJsonObject, fieldJsonCursor } from "./json/index.js"; import { JsonAsTree } from "../jsonDomainSchema.js"; /** @@ -118,7 +122,7 @@ export function testForest(config: ForestTestConfiguration): void { initializeForest( forest, - [singleJsonCursor(data)], + fieldJsonCursor([data]), testRevisionTagCodec, testIdCompressor, ); @@ -138,7 +142,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(toStoredSchema(JsonAsTree.Array))); initializeForest( forest, - [singleJsonCursor([1, 2])], + fieldJsonCursor([[1, 2]]), testRevisionTagCodec, testIdCompressor, ); @@ -173,7 +177,7 @@ export function testForest(config: ForestTestConfiguration): void { it("isEmpty: rootFieldKey", () => { const forest = factory(new TreeStoredSchemaRepository(toStoredSchema(JsonAsTree.Array))); assert(forest.isEmpty); - initializeForest(forest, [singleJsonCursor([])], testRevisionTagCodec, testIdCompressor); + initializeForest(forest, fieldJsonCursor([[]]), testRevisionTagCodec, testIdCompressor); assert(!forest.isEmpty); }); @@ -183,7 +187,7 @@ export function testForest(config: ForestTestConfiguration): void { const insert: DeltaFieldChanges = [{ count: 1, attach: { minor: 1 } }]; applyTestDelta(new Map([[brand("different root"), insert]]), forest, { - build: [{ id: { minor: 1 }, trees: [singleJsonCursor([])] }], + build: [{ id: { minor: 1 }, trees: chunkFromJsonField([[]]) }], }); assert(!forest.isEmpty); }); @@ -200,7 +204,7 @@ export function testForest(config: ForestTestConfiguration): void { initializeForest( forest, - [singleJsonCursor([1, 2])], + fieldJsonCursor([[1, 2]]), testRevisionTagCodec, testIdCompressor, ); @@ -241,7 +245,7 @@ export function testForest(config: ForestTestConfiguration): void { initializeForest( forest, - [singleJsonCursor([1, 2])], + fieldJsonCursor([[1, 2]]), testRevisionTagCodec, testIdCompressor, ); @@ -285,7 +289,7 @@ export function testForest(config: ForestTestConfiguration): void { ); initializeForest( forest, - [singleJsonCursor([1, 2])], + fieldJsonCursor([[1, 2]]), testRevisionTagCodec, testIdCompressor, ); @@ -306,7 +310,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(toStoredSchema(JsonAsTree.Array))); initializeForest( forest, - [singleJsonCursor([1, 2])], + fieldJsonCursor([[1, 2]]), testRevisionTagCodec, testIdCompressor, ); @@ -325,7 +329,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(toStoredSchema(JsonAsTree.Array))); initializeForest( forest, - [singleJsonCursor([1, 2])], + fieldJsonCursor([[1, 2]]), testRevisionTagCodec, testIdCompressor, ); @@ -394,7 +398,7 @@ export function testForest(config: ForestTestConfiguration): void { initializeForest( forest, - [singleJsonCursor([1, 2])], + fieldJsonCursor([[1, 2]]), testRevisionTagCodec, testIdCompressor, ); @@ -423,7 +427,7 @@ export function testForest(config: ForestTestConfiguration): void { const content: JsonCompatible[] = [1, 2]; initializeForest( forest, - content.map(singleJsonCursor), + fieldJsonCursor(content), testRevisionTagCodec, testIdCompressor, ); @@ -480,7 +484,7 @@ export function testForest(config: ForestTestConfiguration): void { const content: JsonCompatible[] = [1, true, "test"]; initializeForest( forest, - content.map(singleJsonCursor), + fieldJsonCursor(content), testRevisionTagCodec, testIdCompressor, ); @@ -502,7 +506,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(schema); initializeForest( forest, - [singleJsonCursor(nestedContent)], + fieldJsonCursor([nestedContent]), testRevisionTagCodec, testIdCompressor, ); @@ -520,7 +524,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(schema); initializeForest( forest, - [singleJsonCursor(nestedContent)], + fieldJsonCursor([nestedContent]), testRevisionTagCodec, testIdCompressor, ); @@ -549,7 +553,7 @@ export function testForest(config: ForestTestConfiguration): void { ]; initializeForest( forest, - content.map(cursorForJsonableTreeNode), + cursorForJsonableTreeField(content), testRevisionTagCodec, testIdCompressor, ); @@ -578,7 +582,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); initializeForest( forest, - [singleJsonCursor(1)], + fieldJsonCursor([1]), testRevisionTagCodec, testIdCompressor, ); @@ -594,7 +598,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); initializeForest( forest, - [singleJsonCursor(1)], + fieldJsonCursor([1]), testRevisionTagCodec, testIdCompressor, ); @@ -615,12 +619,7 @@ export function testForest(config: ForestTestConfiguration): void { it("beforeChange events", () => { const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); - initializeForest( - forest, - [singleJsonCursor(1)], - testRevisionTagCodec, - testIdCompressor, - ); + initializeForest(forest, fieldJsonCursor([1]), testRevisionTagCodec, testIdCompressor); const log: string[] = []; forest.events.on("beforeChange", () => { @@ -637,7 +636,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); initializeForest( forest, - [singleJsonCursor(nestedContent)], + fieldJsonCursor([nestedContent]), testRevisionTagCodec, testIdCompressor, ); @@ -651,12 +650,12 @@ export function testForest(config: ForestTestConfiguration): void { build: [ { id: buildId, - trees: [ - cursorForJsonableTreeNode({ + trees: chunkFromJsonableField([ + { type: brand(booleanSchema.identifier), value: true, - }), - ], + }, + ]), }, ], }); @@ -673,7 +672,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); initializeForest( forest, - [singleJsonCursor(nestedContent)], + fieldJsonCursor([nestedContent]), testRevisionTagCodec, testIdCompressor, ); @@ -687,12 +686,12 @@ export function testForest(config: ForestTestConfiguration): void { build: [ { id: buildId, - trees: [ - cursorForJsonableTreeNode({ + trees: chunkFromJsonableField([ + { type: brand(booleanSchema.identifier), value: true, - }), - ], + }, + ]), }, ], }); @@ -712,7 +711,7 @@ export function testForest(config: ForestTestConfiguration): void { const content: JsonCompatible[] = [1, 2]; initializeForest( forest, - content.map(singleJsonCursor), + fieldJsonCursor(content), testRevisionTagCodec, testIdCompressor, ); @@ -739,7 +738,7 @@ export function testForest(config: ForestTestConfiguration): void { const content: JsonCompatible[] = [1, 2]; initializeForest( forest, - content.map(singleJsonCursor), + fieldJsonCursor(content), testRevisionTagCodec, testIdCompressor, ); @@ -771,7 +770,7 @@ export function testForest(config: ForestTestConfiguration): void { const content: JsonCompatible[] = [1, 2]; initializeForest( forest, - content.map(singleJsonCursor), + fieldJsonCursor(content), testRevisionTagCodec, testIdCompressor, ); @@ -780,7 +779,7 @@ export function testForest(config: ForestTestConfiguration): void { [rootFieldKey, [{ count: 1, attach: buildId }]], ]); applyTestDelta(delta, forest, { - build: [{ id: buildId, trees: [singleJsonCursor(3)] }], + build: [{ id: buildId, trees: chunkFromJsonField([3]) }], }); const reader = forest.allocateCursor(); @@ -810,7 +809,7 @@ export function testForest(config: ForestTestConfiguration): void { }; const delta: DeltaFieldMap = new Map([[rootFieldKey, [moveIn]]]); applyTestDelta(delta, forest, { - build: [{ id: buildId, trees: [singleJsonCursor({ x: 0 })] }], + build: [{ id: buildId, trees: chunkFromJsonField([{ x: 0 }]) }], global: [ { id: buildId, @@ -830,7 +829,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); initializeForest( forest, - [singleJsonCursor(nestedContent)], + fieldJsonCursor([nestedContent]), testRevisionTagCodec, testIdCompressor, ); @@ -868,7 +867,7 @@ export function testForest(config: ForestTestConfiguration): void { const content: JsonCompatible[] = [1, 2]; initializeForest( forest, - content.map(singleJsonCursor), + fieldJsonCursor(content), testRevisionTagCodec, testIdCompressor, ); @@ -880,21 +879,21 @@ export function testForest(config: ForestTestConfiguration): void { build: [ { id: buildId, - trees: [ - cursorForJsonableTreeNode({ + trees: chunkFromJsonableField([ + { type: brand(numberSchema.identifier), value: 3, - }), - ], + }, + ]), }, { id: buildId2, - trees: [ - cursorForJsonableTreeNode({ + trees: chunkFromJsonableField([ + { type: brand(numberSchema.identifier), value: 4, - }), - ], + }, + ]), }, ], global: [ @@ -925,7 +924,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); initializeForest( forest, - [singleJsonCursor(nestedContent)], + fieldJsonCursor([nestedContent]), testRevisionTagCodec, testIdCompressor, ); @@ -953,7 +952,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); initializeForest( forest, - [singleJsonCursor(nestedContent)], + fieldJsonCursor([nestedContent]), testRevisionTagCodec, testIdCompressor, ); @@ -988,7 +987,7 @@ export function testForest(config: ForestTestConfiguration): void { }; const delta: DeltaFieldMap = new Map([[rootFieldKey, [mark]]]); applyTestDelta(delta, forest, { - build: [{ id: buildId, trees: [singleJsonCursor(3)] }], + build: [{ id: buildId, trees: chunkFromJsonField([3]) }], }); const reader = forest.allocateCursor(); @@ -1035,7 +1034,7 @@ export function testForest(config: ForestTestConfiguration): void { const expected: JsonCompatible[] = [{ y: 1 }]; initializeForest( forest, - [singleJsonCursor(nestedContent)], + fieldJsonCursor([nestedContent]), testRevisionTagCodec, testIdCompressor, ); @@ -1047,17 +1046,10 @@ export function testForest(config: ForestTestConfiguration): void { assert.deepEqual(actual, expected); }); it("when moving the last node in the field", () => { - const schemaFactory = new SchemaFactory("moving"); - const NodeSchema = schemaFactory.object("root", { - x: schemaFactory.optional(schemaFactory.number), - y: schemaFactory.optional(schemaFactory.number), - }); - const schema = toStoredSchema(schemaFactory.array(NodeSchema)); - - const forest = factory(new TreeStoredSchemaRepository(schema)); + const forest = factory(new TreeStoredSchemaRepository(jsonSequenceRootSchema)); initializeForest( forest, - [cursorFromInsertable(NodeSchema, { x: 2 })], + fieldJsonCursor([{ x: 2 }]), testRevisionTagCodec, testIdCompressor, ); @@ -1080,7 +1072,7 @@ export function testForest(config: ForestTestConfiguration): void { }; const delta: DeltaFieldMap = new Map([[rootFieldKey, [modify]]]); applyTestDelta(delta, forest); - const expectedCursor = cursorFromInsertable(NodeSchema, { y: 2 }); + const expectedCursor = fieldJsonCursor([{ y: 2 }]); const expected: JsonableTree[] = [jsonableTreeFromCursor(expectedCursor)]; const readCursor = forest.allocateCursor(); moveToDetachedField(forest, readCursor); @@ -1104,7 +1096,7 @@ export function testForest(config: ForestTestConfiguration): void { const content: JsonCompatible[] = [1, 2]; initializeForest( forest, - content.map(singleJsonCursor), + fieldJsonCursor(content), testRevisionTagCodec, testIdCompressor, ); @@ -1112,12 +1104,12 @@ export function testForest(config: ForestTestConfiguration): void { forest.registerAnnouncedVisitor(acquireVisitor); const delta: DeltaFieldMap = new Map([[rootFieldKey, [{ count: 1, attach: buildId }]]]); applyTestDelta(delta, forest, { - build: [{ id: buildId, trees: [singleJsonCursor(3)] }], + build: [{ id: buildId, trees: chunkFromJsonField([3]) }], }); forest.deregisterAnnouncedVisitor(acquireVisitor); applyTestDelta(delta, forest, { - build: [{ id: buildId, trees: [singleJsonCursor(4)] }], + build: [{ id: buildId, trees: chunkFromJsonField([4]) }], }); assert.equal(treesCreated, 1); @@ -1130,7 +1122,7 @@ export function testForest(config: ForestTestConfiguration): void { const forest = factory(new TreeStoredSchemaRepository(toStoredSchema(testTreeSchema))); initializeForest( forest, - [cursorForJsonableTreeNode(data)], + cursorForJsonableTreeField([data]), testRevisionTagCodec, testIdCompressor, ); diff --git a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeEnricher.spec.ts b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeEnricher.spec.ts index 17b1c963104e..158c0ad83b79 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeEnricher.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeEnricher.spec.ts @@ -17,7 +17,7 @@ import { rootFieldKey, tagChange, } from "../../core/index.js"; -import { cursorToJsonObject, singleJsonCursor } from "../json/index.js"; +import { cursorToJsonObject, fieldJsonCursor } from "../json/index.js"; import { typeboxValidator } from "../../external-utilities/index.js"; // eslint-disable-next-line import/no-internal-modules import { optional } from "../../feature-libraries/default-schema/defaultFieldKinds.js"; @@ -94,12 +94,7 @@ export function setupEnricher() { { jsonValidator: typeboxValidator }, ); const forest = buildForest(); - initializeForest( - forest, - [singleJsonCursor(content)], - testRevisionTagCodec, - testIdCompressor, - ); + initializeForest(forest, fieldJsonCursor([content]), testRevisionTagCodec, testIdCompressor); const schema = new TreeStoredSchemaRepository(); const enricher = new SharedTreeReadonlyChangeEnricher( forest, diff --git a/packages/dds/tree/src/test/simple-tree/utils.ts b/packages/dds/tree/src/test/simple-tree/utils.ts index 52e2f45f05fc..ef7a7195a202 100644 --- a/packages/dds/tree/src/test/simple-tree/utils.ts +++ b/packages/dds/tree/src/test/simple-tree/utils.ts @@ -7,7 +7,7 @@ import { assert } from "@fluidframework/core-utils/internal"; import { initializeForest, TreeStoredSchemaRepository } from "../../core/index.js"; import { buildForest, - cursorForMapTreeNode, + cursorForMapTreeField, defaultSchemaPolicy, getSchemaAndPolicy, MockNodeKeyManager, @@ -142,8 +142,8 @@ export function hydrate( ); prepareContentForHydration(mapTree, field.context.checkout.forest); if (mapTree === undefined) return undefined as TreeFieldFromImplicitField; - const cursor = cursorForMapTreeNode(mapTree); - initializeForest(forest, [cursor], testRevisionTagCodec, testIdCompressor, true); + const cursor = cursorForMapTreeField([mapTree]); + initializeForest(forest, cursor, testRevisionTagCodec, testIdCompressor, true); return getTreeNodeForField(field) as TreeFieldFromImplicitField; } diff --git a/packages/dds/tree/src/test/tree/anchorSet.spec.ts b/packages/dds/tree/src/test/tree/anchorSet.spec.ts index 96981cbede02..a68256797f56 100644 --- a/packages/dds/tree/src/test/tree/anchorSet.spec.ts +++ b/packages/dds/tree/src/test/tree/anchorSet.spec.ts @@ -24,10 +24,10 @@ import { makeDetachedFieldIndex, rootFieldKey, } from "../../core/index.js"; -import { cursorForJsonableTreeNode } from "../../feature-libraries/index.js"; import { brand } from "../../util/index.js"; import { applyTestDelta, + chunkFromJsonableField, expectEqualPaths, testIdCompressor, testRevisionTagCodec, @@ -86,7 +86,7 @@ describe("AnchorSet", () => { it("can rebase over insert", () => { const [anchors, anchor1, anchor2, anchor3] = setup(); - const trees = [node, node].map(cursorForJsonableTreeNode); + const trees = chunkFromJsonableField([node, node]); const fieldChanges: DeltaFieldChanges = [{ count: 4 }, { count: 2, attach: buildId }]; applyTestDelta(makeFieldDelta(fieldChanges, makeFieldPath(fieldFoo)), anchors, { build: [{ id: buildId, trees }], @@ -424,9 +424,7 @@ describe("AnchorSet", () => { const build = [ { id: buildId, - trees: [ - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "x" }), - ], + trees: chunkFromJsonableField([{ type: brand(stringSchema.identifier), value: "x" }]), }, ]; applyTestDelta(new Map([[rootFieldKey, [detachMark, insertMark]]]), anchors, { build }); diff --git a/packages/dds/tree/src/test/tree/visitDelta.spec.ts b/packages/dds/tree/src/test/tree/visitDelta.spec.ts index eb3acbcc1f95..9071c2542b4b 100644 --- a/packages/dds/tree/src/test/tree/visitDelta.spec.ts +++ b/packages/dds/tree/src/test/tree/visitDelta.spec.ts @@ -20,14 +20,15 @@ import { } from "../../core/index.js"; import { brand } from "../../util/index.js"; import { + chunkFromJsonField, mintRevisionTag, + nodeCursorsFromChunk, rootFromDeltaFieldMap, testIdCompressor, testRevisionTagCodec, type DeltaParams, } from "../utils.js"; import { deepFreeze } from "@fluidframework/test-runtime-utils/internal"; -import { singleJsonCursor } from "../json/index.js"; function visit( delta: DeltaRoot, @@ -118,7 +119,8 @@ function testTreeVisit( const rootKey: FieldKey = brand("root"); const fooKey: FieldKey = brand("foo"); const barKey: FieldKey = brand("bar"); -const content = singleJsonCursor("X"); +const oneString = chunkFromJsonField(["X"]); +const twoStrings = chunkFromJsonField(["X", "Y"]); const field0: FieldKey = brand("-0"); const field1: FieldKey = brand("-1"); const field2: FieldKey = brand("-2"); @@ -141,11 +143,11 @@ describe("visitDelta", () => { const node = { minor: 42 }; const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - build: [{ id: node, trees: [content] }], + build: [{ id: node, trees: oneString }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], @@ -161,7 +163,7 @@ describe("visitDelta", () => { index.createEntry(node); const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - build: [{ id: node, trees: [content] }], + build: [{ id: node, trees: oneString }], fields: new Map([[rootKey, rootFieldDelta]]), }; assert.throws(() => testDeltaVisit(delta, [], index)); @@ -177,7 +179,7 @@ describe("visitDelta", () => { }, ]; const expected: VisitScript = [ - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["enterField", rootKey], ["enterNode", 0], ["enterField", fooKey], @@ -193,7 +195,7 @@ describe("visitDelta", () => { ["exitField", rootKey], ]; const delta: DeltaRoot = { - build: [{ id: buildId, trees: [content] }], + build: [{ id: buildId, trees: oneString }], fields: new Map([[rootKey, rootFieldDelta]]), }; testDeltaVisit(delta, expected, index); @@ -271,11 +273,11 @@ describe("visitDelta", () => { fields: new Map([[fooKey, [{ count: 42 }, moveOut, moveIn]]]), }, ], - build: [{ id: { minor: 43 }, trees: [content] }], + build: [{ id: { minor: 43 }, trees: oneString }], fields: new Map([[rootKey, [{ count: 1, attach: { minor: 43 } }]]]), }; const expected: VisitScript = [ - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", field0], @@ -500,11 +502,11 @@ describe("visitDelta", () => { const detachId = { minor: 43 }; const delta: DeltaRoot = { rename: [{ oldId: buildId, newId: detachId, count: 1 }], - build: [{ id: buildId, trees: [content] }], + build: [{ id: buildId, trees: oneString }], destroy: [{ id: detachId, count: 1 }], }; const expected: VisitScript = [ - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["enterField", field0], ["detach", { start: 0, end: 1 }, field1], ["exitField", field0], @@ -582,12 +584,12 @@ describe("visitDelta", () => { const fieldChanges: DeltaFieldChanges = [moveOut1, moveOut2, moveIn]; const delta: DeltaRoot = { - build: [{ id: buildId, trees: [content] }], + build: [{ id: buildId, trees: oneString }], fields: new Map([[rootKey, fieldChanges]]), }; const expected: VisitScript = [ - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["enterField", rootKey], ["detach", { start: 0, end: 1 }, field1], ["enterNode", 0], @@ -622,13 +624,13 @@ describe("visitDelta", () => { const fieldChanges: DeltaFieldChanges = [replace]; const delta: DeltaRoot = { - build: [{ id: buildId, trees: [content, content] }], + build: [{ id: buildId, trees: twoStrings }], fields: new Map([[rootKey, fieldChanges]]), }; const expected: VisitScript = [ - ["create", [content], field0], - ["create", [content], field1], + ["create", nodeCursorsFromChunk(oneString), field0], + ["create", nodeCursorsFromChunk(oneString), field1], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], @@ -735,11 +737,11 @@ describe("visitDelta", () => { it("transient insert", () => { const index = makeDetachedFieldIndex("", testRevisionTagCodec, testIdCompressor); const delta: DeltaRoot = { - build: [{ id: { minor: 42 }, trees: [content] }], + build: [{ id: { minor: 42 }, trees: oneString }], rename: [{ oldId: { minor: 42 }, count: 1, newId: { minor: 43 } }], }; const expected: VisitScript = [ - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["enterField", field0], ["detach", { start: 0, end: 1 }, field1], ["exitField", field0], @@ -761,12 +763,12 @@ describe("visitDelta", () => { const buildId = { minor: 42 }; const detachId = { minor: 43 }; const delta: DeltaRoot = { - build: [{ id: buildId, trees: [content] }], + build: [{ id: buildId, trees: oneString }], global: [{ id: buildId, fields: new Map([[barKey, [moveOut, moveIn]]]) }], rename: [{ oldId: buildId, count: 1, newId: detachId }], }; const expected: VisitScript = [ - ["create", [content], field0], // field0: buildId + ["create", nodeCursorsFromChunk(oneString), field0], // field0: buildId ["enterField", field0], ["enterNode", 0], ["enterField", barKey], @@ -956,11 +958,11 @@ describe("visitDelta", () => { newId: node1, }; const delta = { - build: [{ id: buildId, trees: [content] }], + build: [{ id: buildId, trees: oneString }], rename: [renameOldNode, renameNewNode], }; const expected: VisitScript = [ - ["create", [content], field1], // field1: buildId + ["create", nodeCursorsFromChunk(oneString), field1], // field1: buildId ["enterField", field0], // field0: node1 ["detach", { start: 0, end: 1 }, field2], // field2: detachId ["exitField", field0], @@ -981,14 +983,14 @@ describe("visitDelta", () => { const node = { minor: 42 }; const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - refreshers: [{ id: node, trees: [content] }], + refreshers: [{ id: node, trees: oneString }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1015,14 +1017,14 @@ describe("visitDelta", () => { ["enterField", rootKey], ["enterNode", 0], ["enterField", fooKey], - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["attach", field0, 1, 0], ["exitField", fooKey], ["exitNode", 0], ["exitField", rootKey], ]; const delta: DeltaRoot = { - refreshers: [{ id: buildId, trees: [content] }], + refreshers: [{ id: buildId, trees: oneString }], fields: new Map([[rootKey, rootFieldDelta]]), }; testDeltaVisit(delta, expected, index); @@ -1034,14 +1036,14 @@ describe("visitDelta", () => { const node = { minor: 42 }; const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: { minor: 43 } }]; const delta: DeltaRoot = { - refreshers: [{ id: node, trees: [content, content] }], + refreshers: [{ id: node, trees: twoStrings }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1054,8 +1056,8 @@ describe("visitDelta", () => { const refresherId = { minor: 42 }; const buildId = { minor: 43 }; const expected: VisitScript = [ - ["create", [content], field0], - ["create", [content], field1], + ["create", nodeCursorsFromChunk(oneString), field0], + ["create", nodeCursorsFromChunk(oneString), field1], ["enterField", field1], ["enterNode", 0], ["enterField", fooKey], @@ -1077,8 +1079,8 @@ describe("visitDelta", () => { fields: new Map([[fooKey, [{ count: 1, attach: buildId }]]]), }, ], - refreshers: [{ id: refresherId, trees: [content] }], - build: [{ id: buildId, trees: [content] }], + refreshers: [{ id: refresherId, trees: oneString }], + build: [{ id: buildId, trees: oneString }], // TODO the global was in this so it might've changed the expected value // fields: new Map([[rootKey, rootFieldDelta]]), }; @@ -1097,7 +1099,7 @@ describe("visitDelta", () => { newId: moveId, }; const delta = { - refreshers: [{ id: node1, trees: [content] }], + refreshers: [{ id: node1, trees: oneString }], rename: [rename], }; const expected: VisitScript = [ @@ -1118,9 +1120,9 @@ describe("visitDelta", () => { const index = makeDetachedFieldIndex("", testRevisionTagCodec, testIdCompressor); const node = { minor: 42 }; const delta: DeltaRoot = { - build: [{ id: node, trees: [content] }], + build: [{ id: node, trees: oneString }], }; - const expected: VisitScript = [["create", [content], field0]]; + const expected: VisitScript = [["create", nodeCursorsFromChunk(oneString), field0]]; const revision = mintRevisionTag(); testDeltaVisit(delta, expected, index, revision); assert.deepEqual(Array.from(index.entries()), [ @@ -1183,13 +1185,13 @@ describe("visitDelta", () => { const rootChanges: DeltaFieldChanges = [replace]; const delta: DeltaRoot = { - build: [{ id: buildId, trees: [content, content] }], + build: [{ id: buildId, trees: twoStrings }], fields: new Map([[rootKey, rootChanges]]), }; const expected: VisitScript = [ - ["create", [content], field0], - ["create", [content], field1], + ["create", nodeCursorsFromChunk(oneString), field0], + ["create", nodeCursorsFromChunk(oneString), field1], ["enterField", rootKey], ["detach", { start: 0, end: 1 }, field2], ["detach", { start: 0, end: 1 }, field3], @@ -1216,8 +1218,8 @@ describe("visitDelta", () => { const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node2 }]; const delta: DeltaRoot = { refreshers: [ - { id: node, trees: [content] }, - { id: node2, trees: [content] }, + { id: node, trees: oneString }, + { id: node2, trees: oneString }, ], fields: new Map([[rootKey, rootFieldDelta]]), }; @@ -1225,7 +1227,7 @@ describe("visitDelta", () => { ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1239,7 +1241,7 @@ describe("visitDelta", () => { index.createEntry(node, undefined, 1); const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - refreshers: [{ id: node, trees: [content] }], + refreshers: [{ id: node, trees: oneString }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ @@ -1258,12 +1260,12 @@ describe("visitDelta", () => { const node = { minor: 42 }; const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - build: [{ id: node, trees: [content] }], - refreshers: [{ id: node, trees: [content] }], + build: [{ id: node, trees: oneString }], + refreshers: [{ id: node, trees: oneString }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ - ["create", [content], field0], + ["create", nodeCursorsFromChunk(oneString), field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], diff --git a/packages/dds/tree/src/test/utils.ts b/packages/dds/tree/src/test/utils.ts index 24c77dd0e20a..5104cd9dd4fb 100644 --- a/packages/dds/tree/src/test/utils.ts +++ b/packages/dds/tree/src/test/utils.ts @@ -93,12 +93,12 @@ import { type RevertibleAlphaFactory, type DeltaDetachedNodeChanges, type DeltaDetachedNodeRename, + type ExclusiveMapTree, } from "../core/index.js"; import { typeboxValidator } from "../external-utilities/index.js"; import { type NodeKeyManager, buildForest, - cursorForMapTreeNode, defaultSchemaPolicy, jsonableTreeFromFieldCursor, jsonableTreeFromForest, @@ -107,6 +107,11 @@ import { MockNodeKeyManager, cursorForMapTreeField, type IDefaultEditBuilder, + type TreeChunk, + mapTreeFieldFromCursor, + chunkTree, + defaultChunkPolicy, + cursorForJsonableTreeField, } from "../feature-libraries/index.js"; // eslint-disable-next-line import/no-internal-modules import { makeSchemaCodec } from "../feature-libraries/schema-index/codec.js"; @@ -154,7 +159,7 @@ import { } from "../util/index.js"; import { isFluidHandle, toFluidHandleInternal } from "@fluidframework/runtime-utils/internal"; import type { Client } from "@fluid-private/test-dds-utils"; -import { cursorToJsonObject, singleJsonCursor } from "./json/index.js"; +import { cursorToJsonObject, fieldJsonCursor, singleJsonCursor } from "./json/index.js"; // eslint-disable-next-line import/no-internal-modules import type { TreeSimpleContent } from "./feature-libraries/flex-tree/utils.js"; import type { Transactor } from "../shared-tree-core/index.js"; @@ -534,8 +539,8 @@ export function assertDeltaFieldMapEqual(a: DeltaFieldMap, b: DeltaFieldMap): vo * Assert two Delta are equal, handling cursors. */ export function assertDeltaEqual(a: DeltaRoot, b: DeltaRoot): void { - const aTree = mapRootChanges(a, mapTreeFromCursor); - const bTree = mapRootChanges(b, mapTreeFromCursor); + const aTree = mapRootChanges(a, chunkToMapTreeField); + const bTree = mapRootChanges(b, chunkToMapTreeField); assert.deepStrictEqual(aTree, bTree); } @@ -790,11 +795,7 @@ export function flexTreeViewWithContent( export function forestWithContent(content: TreeStoredContent): IEditableForest { const forest = buildForest(); const fieldCursor = normalizeNewFieldContent(content.initialTree); - // TODO:AB6712 Make the delta format accept a single cursor in Field mode. - const nodeCursors = mapCursorField(fieldCursor, (c) => - cursorForMapTreeNode(mapTreeFromCursor(c)), - ); - initializeForest(forest, nodeCursors, testRevisionTagCodec, testIdCompressor); + initializeForest(forest, fieldCursor, testRevisionTagCodec, testIdCompressor); return forest; } @@ -1331,3 +1332,21 @@ export function moveWithin( ) { editor.move(field, sourceIndex, count, field, destIndex); } + +export function chunkFromJsonField(field: JsonCompatible[]): TreeChunk { + const cursor = fieldJsonCursor(field); + return chunkTree(cursor, { idCompressor: testIdCompressor, policy: defaultChunkPolicy }); +} + +export function chunkFromJsonableField(field: JsonableTree[]): TreeChunk { + const cursor = cursorForJsonableTreeField(field); + return chunkTree(cursor, { idCompressor: testIdCompressor, policy: defaultChunkPolicy }); +} + +export function chunkToMapTreeField(chunk: TreeChunk): ExclusiveMapTree[] { + return mapTreeFieldFromCursor(chunk.cursor()); +} + +export function nodeCursorsFromChunk(trees: TreeChunk): ITreeCursorSynchronous[] { + return mapCursorField(trees.cursor(), (c) => c.fork()); +} From b6fcd2b93860b5993f4fb5ad03b6b5018ad35d72 Mon Sep 17 00:00:00 2001 From: Yann Achard Date: Thu, 6 Mar 2025 10:30:47 -0800 Subject: [PATCH 2/7] fix all but one test --- .../src/feature-libraries/initializeForest.ts | 11 +- .../defaultChangeFamily.spec.ts | 11 +- packages/dds/tree/src/test/forestTestSuite.ts | 4 +- .../dds/tree/src/test/tree/visitDelta.spec.ts | 112 ++++++++++-------- packages/dds/tree/src/test/utils.ts | 12 +- 5 files changed, 85 insertions(+), 65 deletions(-) diff --git a/packages/dds/tree/src/feature-libraries/initializeForest.ts b/packages/dds/tree/src/feature-libraries/initializeForest.ts index 195f2e22dab7..1145e262ddcd 100644 --- a/packages/dds/tree/src/feature-libraries/initializeForest.ts +++ b/packages/dds/tree/src/feature-libraries/initializeForest.ts @@ -13,10 +13,11 @@ import { type RevisionTagCodec, combineVisitors, deltaForRootInitialization, + emptyDelta, makeDetachedFieldIndex, visitDelta, } from "../core/index.js"; -import { chunkTree, defaultChunkPolicy } from "./chunked-forest/index.js"; +import { chunkFieldSingle, defaultChunkPolicy } from "./chunked-forest/index.js"; /** * Initializes the given forest with the given content. @@ -36,8 +37,12 @@ export function initializeForest( visitAnchors = false, ): void { assert(forest.isEmpty, 0x747 /* forest must be empty */); - const chunk = chunkTree(content, { idCompressor, policy: defaultChunkPolicy }); - const delta: DeltaRoot = deltaForRootInitialization(chunk); + const delta: DeltaRoot = + content.getFieldLength() === 0 + ? emptyDelta + : deltaForRootInitialization( + chunkFieldSingle(content, { idCompressor, policy: defaultChunkPolicy }), + ); let visitor = forest.acquireVisitor(); if (visitAnchors) { assert(forest.anchors.isEmpty(), 0x9b7 /* anchor set must be empty */); diff --git a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts index 21005afe251a..ccc7691b7d6f 100644 --- a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts @@ -7,6 +7,7 @@ import { strict as assert } from "node:assert"; import { type DeltaRoot, + EmptyKey, type FieldKey, type IForestSubscription, type JsonableTree, @@ -974,18 +975,12 @@ describe("DefaultEditBuilder", () => { it("Moving 0 items does nothing.", () => { const { builder, forest } = initializeEditableForest({ - type: brand(JsonAsTree.JsonObject.identifier), - fields: { - foo: [], - }, + type: brand(JsonAsTree.Array.identifier), }); builder.move({ parent: root, field: fooKey }, 0, 0, { parent: root, field: fooKey }, 0); const treeView = toJsonableTreeFromForest(forest); const expected: JsonableTree = { - type: brand(JsonAsTree.JsonObject.identifier), - fields: { - foo: [], - }, + type: brand(JsonAsTree.Array.identifier), }; assert.deepEqual(treeView, [expected]); }); diff --git a/packages/dds/tree/src/test/forestTestSuite.ts b/packages/dds/tree/src/test/forestTestSuite.ts index 6b29e48f398c..e3ddbf376606 100644 --- a/packages/dds/tree/src/test/forestTestSuite.ts +++ b/packages/dds/tree/src/test/forestTestSuite.ts @@ -60,7 +60,7 @@ import { toStoredSchema, } from "../simple-tree/index.js"; import { jsonSequenceRootSchema } from "./sequenceRootUtils.js"; -import { cursorToJsonObject, fieldJsonCursor } from "./json/index.js"; +import { cursorToJsonObject, fieldJsonCursor, singleJsonCursor } from "./json/index.js"; import { JsonAsTree } from "../jsonDomainSchema.js"; /** @@ -1069,7 +1069,7 @@ export function testForest(config: ForestTestConfiguration): void { }; const delta: DeltaFieldMap = new Map([[rootFieldKey, [modify]]]); applyTestDelta(delta, forest); - const expectedCursor = fieldJsonCursor([{ y: 2 }]); + const expectedCursor = singleJsonCursor({ y: 2 }); const expected: JsonableTree[] = [jsonableTreeFromCursor(expectedCursor)]; const readCursor = forest.allocateCursor(); moveToDetachedField(forest, readCursor); diff --git a/packages/dds/tree/src/test/tree/visitDelta.spec.ts b/packages/dds/tree/src/test/tree/visitDelta.spec.ts index 9071c2542b4b..a76715a855ec 100644 --- a/packages/dds/tree/src/test/tree/visitDelta.spec.ts +++ b/packages/dds/tree/src/test/tree/visitDelta.spec.ts @@ -14,6 +14,8 @@ import { type DeltaVisitor, type DetachedFieldIndex, type FieldKey, + type ITreeCursorSynchronous, + type MapTree, type RevisionTag, makeDetachedFieldIndex, visitDelta, @@ -21,14 +23,15 @@ import { import { brand } from "../../util/index.js"; import { chunkFromJsonField, + chunkToMapTreeField, mintRevisionTag, - nodeCursorsFromChunk, rootFromDeltaFieldMap, testIdCompressor, testRevisionTagCodec, type DeltaParams, } from "../utils.js"; import { deepFreeze } from "@fluidframework/test-runtime-utils/internal"; +import { mapTreeFromCursor } from "../../feature-libraries/index.js"; function visit( delta: DeltaRoot, @@ -50,7 +53,9 @@ type CallSignatures = { [K in keyof T]: T[K] extends (...args: any) => any ? [K, ...Parameters] : never; }; type PropType = T[keyof T]; -type VisitCall = PropType>; +type VisitCall = + | PropType>> + | ["create", MapTree[], FieldKey]; type VisitScript = VisitCall[]; const visitorMethods: (keyof DeltaVisitor)[] = [ @@ -82,9 +87,17 @@ function testDeltaVisit( let callIndex = 0; const result: VisitScript = []; const makeChecker = - (name: string) => + (name: keyof DeltaVisitor) => (...args: unknown[]) => { - result.push([name, ...args] as VisitCall); + const call: VisitCall = + name === "create" + ? ([ + name, + (args[0] as ITreeCursorSynchronous[]).map(mapTreeFromCursor), + args[1] as FieldKey, + ] as VisitCall) + : ([name, ...args] as VisitCall); + result.push(call); // To break when the first off script event happens, enable this line: // assert.deepStrictEqual([name, ...args], expected[callIndex]); callIndex += 1; @@ -119,8 +132,9 @@ function testTreeVisit( const rootKey: FieldKey = brand("root"); const fooKey: FieldKey = brand("foo"); const barKey: FieldKey = brand("bar"); -const oneString = chunkFromJsonField(["X"]); -const twoStrings = chunkFromJsonField(["X", "Y"]); +const chunkX = chunkFromJsonField(["X"]); +const chunkY = chunkFromJsonField(["Y"]); +const chunkXY = chunkFromJsonField(["X", "Y"]); const field0: FieldKey = brand("-0"); const field1: FieldKey = brand("-1"); const field2: FieldKey = brand("-2"); @@ -143,11 +157,11 @@ describe("visitDelta", () => { const node = { minor: 42 }; const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - build: [{ id: node, trees: oneString }], + build: [{ id: node, trees: chunkX }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], @@ -163,7 +177,7 @@ describe("visitDelta", () => { index.createEntry(node); const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - build: [{ id: node, trees: oneString }], + build: [{ id: node, trees: chunkX }], fields: new Map([[rootKey, rootFieldDelta]]), }; assert.throws(() => testDeltaVisit(delta, [], index)); @@ -179,7 +193,7 @@ describe("visitDelta", () => { }, ]; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["enterField", rootKey], ["enterNode", 0], ["enterField", fooKey], @@ -195,7 +209,7 @@ describe("visitDelta", () => { ["exitField", rootKey], ]; const delta: DeltaRoot = { - build: [{ id: buildId, trees: oneString }], + build: [{ id: buildId, trees: chunkX }], fields: new Map([[rootKey, rootFieldDelta]]), }; testDeltaVisit(delta, expected, index); @@ -273,11 +287,11 @@ describe("visitDelta", () => { fields: new Map([[fooKey, [{ count: 42 }, moveOut, moveIn]]]), }, ], - build: [{ id: { minor: 43 }, trees: oneString }], + build: [{ id: { minor: 43 }, trees: chunkX }], fields: new Map([[rootKey, [{ count: 1, attach: { minor: 43 } }]]]), }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", field0], @@ -502,11 +516,11 @@ describe("visitDelta", () => { const detachId = { minor: 43 }; const delta: DeltaRoot = { rename: [{ oldId: buildId, newId: detachId, count: 1 }], - build: [{ id: buildId, trees: oneString }], + build: [{ id: buildId, trees: chunkX }], destroy: [{ id: detachId, count: 1 }], }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["enterField", field0], ["detach", { start: 0, end: 1 }, field1], ["exitField", field0], @@ -584,12 +598,12 @@ describe("visitDelta", () => { const fieldChanges: DeltaFieldChanges = [moveOut1, moveOut2, moveIn]; const delta: DeltaRoot = { - build: [{ id: buildId, trees: oneString }], + build: [{ id: buildId, trees: chunkX }], fields: new Map([[rootKey, fieldChanges]]), }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["enterField", rootKey], ["detach", { start: 0, end: 1 }, field1], ["enterNode", 0], @@ -624,13 +638,13 @@ describe("visitDelta", () => { const fieldChanges: DeltaFieldChanges = [replace]; const delta: DeltaRoot = { - build: [{ id: buildId, trees: twoStrings }], + build: [{ id: buildId, trees: chunkXY }], fields: new Map([[rootKey, fieldChanges]]), }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], - ["create", nodeCursorsFromChunk(oneString), field1], + ["create", chunkToMapTreeField(chunkX), field0], + ["create", chunkToMapTreeField(chunkY), field1], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], @@ -737,11 +751,11 @@ describe("visitDelta", () => { it("transient insert", () => { const index = makeDetachedFieldIndex("", testRevisionTagCodec, testIdCompressor); const delta: DeltaRoot = { - build: [{ id: { minor: 42 }, trees: oneString }], + build: [{ id: { minor: 42 }, trees: chunkX }], rename: [{ oldId: { minor: 42 }, count: 1, newId: { minor: 43 } }], }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["enterField", field0], ["detach", { start: 0, end: 1 }, field1], ["exitField", field0], @@ -763,12 +777,12 @@ describe("visitDelta", () => { const buildId = { minor: 42 }; const detachId = { minor: 43 }; const delta: DeltaRoot = { - build: [{ id: buildId, trees: oneString }], + build: [{ id: buildId, trees: chunkX }], global: [{ id: buildId, fields: new Map([[barKey, [moveOut, moveIn]]]) }], rename: [{ oldId: buildId, count: 1, newId: detachId }], }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], // field0: buildId + ["create", chunkToMapTreeField(chunkX), field0], // field0: buildId ["enterField", field0], ["enterNode", 0], ["enterField", barKey], @@ -958,11 +972,11 @@ describe("visitDelta", () => { newId: node1, }; const delta = { - build: [{ id: buildId, trees: oneString }], + build: [{ id: buildId, trees: chunkX }], rename: [renameOldNode, renameNewNode], }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field1], // field1: buildId + ["create", chunkToMapTreeField(chunkX), field1], // field1: buildId ["enterField", field0], // field0: node1 ["detach", { start: 0, end: 1 }, field2], // field2: detachId ["exitField", field0], @@ -983,14 +997,14 @@ describe("visitDelta", () => { const node = { minor: 42 }; const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - refreshers: [{ id: node, trees: oneString }], + refreshers: [{ id: node, trees: chunkX }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1017,14 +1031,14 @@ describe("visitDelta", () => { ["enterField", rootKey], ["enterNode", 0], ["enterField", fooKey], - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["attach", field0, 1, 0], ["exitField", fooKey], ["exitNode", 0], ["exitField", rootKey], ]; const delta: DeltaRoot = { - refreshers: [{ id: buildId, trees: oneString }], + refreshers: [{ id: buildId, trees: chunkX }], fields: new Map([[rootKey, rootFieldDelta]]), }; testDeltaVisit(delta, expected, index); @@ -1036,14 +1050,14 @@ describe("visitDelta", () => { const node = { minor: 42 }; const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: { minor: 43 } }]; const delta: DeltaRoot = { - refreshers: [{ id: node, trees: twoStrings }], + refreshers: [{ id: node, trees: chunkXY }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkY), field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1056,8 +1070,8 @@ describe("visitDelta", () => { const refresherId = { minor: 42 }; const buildId = { minor: 43 }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], - ["create", nodeCursorsFromChunk(oneString), field1], + ["create", chunkToMapTreeField(chunkX), field0], + ["create", chunkToMapTreeField(chunkX), field1], ["enterField", field1], ["enterNode", 0], ["enterField", fooKey], @@ -1079,8 +1093,8 @@ describe("visitDelta", () => { fields: new Map([[fooKey, [{ count: 1, attach: buildId }]]]), }, ], - refreshers: [{ id: refresherId, trees: oneString }], - build: [{ id: buildId, trees: oneString }], + refreshers: [{ id: refresherId, trees: chunkX }], + build: [{ id: buildId, trees: chunkX }], // TODO the global was in this so it might've changed the expected value // fields: new Map([[rootKey, rootFieldDelta]]), }; @@ -1099,7 +1113,7 @@ describe("visitDelta", () => { newId: moveId, }; const delta = { - refreshers: [{ id: node1, trees: oneString }], + refreshers: [{ id: node1, trees: chunkX }], rename: [rename], }; const expected: VisitScript = [ @@ -1120,9 +1134,9 @@ describe("visitDelta", () => { const index = makeDetachedFieldIndex("", testRevisionTagCodec, testIdCompressor); const node = { minor: 42 }; const delta: DeltaRoot = { - build: [{ id: node, trees: oneString }], + build: [{ id: node, trees: chunkX }], }; - const expected: VisitScript = [["create", nodeCursorsFromChunk(oneString), field0]]; + const expected: VisitScript = [["create", chunkToMapTreeField(chunkX), field0]]; const revision = mintRevisionTag(); testDeltaVisit(delta, expected, index, revision); assert.deepEqual(Array.from(index.entries()), [ @@ -1185,13 +1199,13 @@ describe("visitDelta", () => { const rootChanges: DeltaFieldChanges = [replace]; const delta: DeltaRoot = { - build: [{ id: buildId, trees: twoStrings }], + build: [{ id: buildId, trees: chunkXY }], fields: new Map([[rootKey, rootChanges]]), }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], - ["create", nodeCursorsFromChunk(oneString), field1], + ["create", chunkToMapTreeField(chunkX), field0], + ["create", chunkToMapTreeField(chunkY), field1], ["enterField", rootKey], ["detach", { start: 0, end: 1 }, field2], ["detach", { start: 0, end: 1 }, field3], @@ -1218,8 +1232,8 @@ describe("visitDelta", () => { const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node2 }]; const delta: DeltaRoot = { refreshers: [ - { id: node, trees: oneString }, - { id: node2, trees: oneString }, + { id: node, trees: chunkX }, + { id: node2, trees: chunkX }, ], fields: new Map([[rootKey, rootFieldDelta]]), }; @@ -1227,7 +1241,7 @@ describe("visitDelta", () => { ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1241,7 +1255,7 @@ describe("visitDelta", () => { index.createEntry(node, undefined, 1); const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - refreshers: [{ id: node, trees: oneString }], + refreshers: [{ id: node, trees: chunkX }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ @@ -1260,12 +1274,12 @@ describe("visitDelta", () => { const node = { minor: 42 }; const rootFieldDelta: DeltaFieldChanges = [{ count: 1, attach: node }]; const delta: DeltaRoot = { - build: [{ id: node, trees: oneString }], - refreshers: [{ id: node, trees: oneString }], + build: [{ id: node, trees: chunkX }], + refreshers: [{ id: node, trees: chunkX }], fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ - ["create", nodeCursorsFromChunk(oneString), field0], + ["create", chunkToMapTreeField(chunkX), field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], diff --git a/packages/dds/tree/src/test/utils.ts b/packages/dds/tree/src/test/utils.ts index 6e79aa245c43..7deb0d5689f7 100644 --- a/packages/dds/tree/src/test/utils.ts +++ b/packages/dds/tree/src/test/utils.ts @@ -108,10 +108,10 @@ import { type IDefaultEditBuilder, type TreeChunk, mapTreeFieldFromCursor, - chunkTree, defaultChunkPolicy, cursorForJsonableTreeField, initializeForest, + chunkFieldSingle, } from "../feature-libraries/index.js"; // eslint-disable-next-line import/no-internal-modules import { makeSchemaCodec } from "../feature-libraries/schema-index/codec.js"; @@ -1335,12 +1335,18 @@ export function moveWithin( export function chunkFromJsonField(field: JsonCompatible[]): TreeChunk { const cursor = fieldJsonCursor(field); - return chunkTree(cursor, { idCompressor: testIdCompressor, policy: defaultChunkPolicy }); + return chunkFieldSingle(cursor, { + idCompressor: testIdCompressor, + policy: defaultChunkPolicy, + }); } export function chunkFromJsonableField(field: JsonableTree[]): TreeChunk { const cursor = cursorForJsonableTreeField(field); - return chunkTree(cursor, { idCompressor: testIdCompressor, policy: defaultChunkPolicy }); + return chunkFieldSingle(cursor, { + idCompressor: testIdCompressor, + policy: defaultChunkPolicy, + }); } export function chunkToMapTreeField(chunk: TreeChunk): ExclusiveMapTree[] { From e1179d63818be394f20173f500710507a862f611 Mon Sep 17 00:00:00 2001 From: Yann Achard Date: Thu, 6 Mar 2025 11:45:12 -0800 Subject: [PATCH 3/7] before using more TreeChunks --- packages/dds/tree/src/core/forest/forest.ts | 10 ++++++++++ packages/dds/tree/src/core/tree/chunk.ts | 2 +- .../feature-libraries/chunked-forest/chunkedForest.ts | 6 +++++- .../tree/src/feature-libraries/chunked-forest/index.ts | 1 + packages/dds/tree/src/feature-libraries/index.ts | 1 + .../dds/tree/src/feature-libraries/initializeForest.ts | 5 +---- .../feature-libraries/object-forest/objectForest.ts | 6 ++++++ packages/dds/tree/src/shared-tree/schematizeTree.ts | 3 ++- .../dds/tree/src/shared-tree/schematizingTreeView.ts | 5 ++++- .../tree/src/test/feature-libraries/flex-tree/utils.ts | 9 ++++++--- packages/dds/tree/src/test/utils.ts | 9 +++++---- 11 files changed, 42 insertions(+), 15 deletions(-) diff --git a/packages/dds/tree/src/core/forest/forest.ts b/packages/dds/tree/src/core/forest/forest.ts index 3913fdab00ad..f64417fc37d0 100644 --- a/packages/dds/tree/src/core/forest/forest.ts +++ b/packages/dds/tree/src/core/forest/forest.ts @@ -14,6 +14,7 @@ import { type DetachedField, type ITreeCursor, type ITreeCursorSynchronous, + type TreeChunk, type UpPath, detachedFieldAsKey, rootField, @@ -82,6 +83,15 @@ export interface IForestSubscription { */ clone(schema: TreeStoredSchemaSubscription, anchors: AnchorSet): IEditableForest; + /** + * Generate a TreeChunk for the content in the given field cursor. + * This can be used to chunk data that is then inserted into the forest. + * + * @remarks + * Like {@link chunkField}, but forces the results into a single TreeChunk. + */ + chunkField(cursor: ITreeCursorSynchronous): TreeChunk; + /** * Allocates a cursor in the "cleared" state. * @param source - optional string identifying the source of the cursor for debugging purposes when cursors are not properly cleaned up. diff --git a/packages/dds/tree/src/core/tree/chunk.ts b/packages/dds/tree/src/core/tree/chunk.ts index 61072ad361b0..8e531cd4a4e8 100644 --- a/packages/dds/tree/src/core/tree/chunk.ts +++ b/packages/dds/tree/src/core/tree/chunk.ts @@ -6,7 +6,7 @@ import { assert } from "@fluidframework/core-utils/internal"; import type { ReferenceCounted } from "../../util/index.js"; -import type { FieldKey } from "../schema-stored/index.js"; +import type { FieldKey, TreeNodeSchemaIdentifier } from "../schema-stored/index.js"; import { rootFieldKey } from "./types.js"; import { CursorLocationType, diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts index 81f509c0d6d1..ad20bed2918b 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts @@ -43,7 +43,7 @@ import { } from "../../util/index.js"; import { BasicChunk, BasicChunkCursor, type SiblingsOrKey } from "./basicChunk.js"; -import { type IChunker, basicChunkTree, chunkTree } from "./chunkTree.js"; +import { type IChunker, basicChunkTree, chunkFieldSingle, chunkTree } from "./chunkTree.js"; import type { IIdCompressor } from "@fluidframework/id-compressor"; function makeRoot(): BasicChunk { @@ -90,6 +90,10 @@ export class ChunkedForest implements IEditableForest { return new ChunkedForest(this.roots, schema, this.chunker.clone(schema), anchors); } + public chunkField(cursor: ITreeCursorSynchronous): TreeChunk { + return chunkFieldSingle(cursor, { idCompressor: this.idCompressor, policy: this.chunker }); + } + public forgetAnchor(anchor: Anchor): void { this.anchors.forget(anchor); } diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/index.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/index.ts index 65146004d157..0c9667191b4a 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/index.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/index.ts @@ -14,6 +14,7 @@ export { chunkField, } from "./chunkTree.js"; export { buildChunkedForest } from "./chunkedForest.js"; +export { emptyChunk } from "./emptyChunk.js"; export { EncodedFieldBatch, type FieldBatch, diff --git a/packages/dds/tree/src/feature-libraries/index.ts b/packages/dds/tree/src/feature-libraries/index.ts index f3e2e88fa40a..c5635cfd1fa9 100644 --- a/packages/dds/tree/src/feature-libraries/index.ts +++ b/packages/dds/tree/src/feature-libraries/index.ts @@ -99,6 +99,7 @@ export { chunkFieldSingle, buildChunkedForest, defaultChunkPolicy, + emptyChunk, type FieldBatch, type FieldBatchCodec, makeTreeChunker, diff --git a/packages/dds/tree/src/feature-libraries/initializeForest.ts b/packages/dds/tree/src/feature-libraries/initializeForest.ts index 1145e262ddcd..95ebbbc23028 100644 --- a/packages/dds/tree/src/feature-libraries/initializeForest.ts +++ b/packages/dds/tree/src/feature-libraries/initializeForest.ts @@ -17,7 +17,6 @@ import { makeDetachedFieldIndex, visitDelta, } from "../core/index.js"; -import { chunkFieldSingle, defaultChunkPolicy } from "./chunked-forest/index.js"; /** * Initializes the given forest with the given content. @@ -40,9 +39,7 @@ export function initializeForest( const delta: DeltaRoot = content.getFieldLength() === 0 ? emptyDelta - : deltaForRootInitialization( - chunkFieldSingle(content, { idCompressor, policy: defaultChunkPolicy }), - ); + : deltaForRootInitialization(forest.chunkField(content)); let visitor = forest.acquireVisitor(); if (visitAnchors) { assert(forest.anchors.isEmpty(), 0x9b7 /* anchor set must be empty */); diff --git a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts index 96000c284723..60223bd2a09d 100644 --- a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts +++ b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts @@ -27,6 +27,7 @@ import { type PathRootPrefix, type PlaceIndex, type Range, + type TreeChunk, TreeNavigationResult, type TreeNodeSchemaIdentifier, type TreeStoredSchemaSubscription, @@ -45,6 +46,7 @@ import { } from "../../util/index.js"; import { cursorForMapTreeNode, mapTreeFromCursor } from "../mapTreeCursor.js"; import { type CursorWithNode, SynchronousCursor } from "../treeCursorUtils.js"; +import { chunkFieldSingle, defaultChunkPolicy } from "../chunked-forest/index.js"; /** A `MapTree` with mutable fields */ interface MutableMapTree extends MapTree { @@ -106,6 +108,10 @@ export class ObjectForest implements IEditableForest { return new ObjectForest(anchors, this.additionalAsserts, this.roots); } + public chunkField(cursor: ITreeCursorSynchronous): TreeChunk { + return chunkFieldSingle(cursor, { idCompressor: undefined, policy: defaultChunkPolicy }); + } + public forgetAnchor(anchor: Anchor): void { this.anchors.forget(anchor); } diff --git a/packages/dds/tree/src/shared-tree/schematizeTree.ts b/packages/dds/tree/src/shared-tree/schematizeTree.ts index 6a9b60e35bc1..3fd13bc38b4d 100644 --- a/packages/dds/tree/src/shared-tree/schematizeTree.ts +++ b/packages/dds/tree/src/shared-tree/schematizeTree.ts @@ -19,6 +19,7 @@ import { cursorForMapTreeField, defaultSchemaPolicy, mapTreeFromCursor, + type TreeChunk, } from "../feature-libraries/index.js"; import { fail, isReadonlyArray } from "../util/index.js"; @@ -269,5 +270,5 @@ export interface TreeStoredContent { * Default tree content to initialize the tree with iff the tree is uninitialized * (meaning it does not even have any schema set at all). */ - readonly initialTree: readonly ITreeCursorSynchronous[] | ITreeCursorSynchronous | undefined; + readonly initialTree: TreeChunk | undefined; } diff --git a/packages/dds/tree/src/shared-tree/schematizingTreeView.ts b/packages/dds/tree/src/shared-tree/schematizingTreeView.ts index 156fb08fb7ed..9496c4252f65 100644 --- a/packages/dds/tree/src/shared-tree/schematizingTreeView.ts +++ b/packages/dds/tree/src/shared-tree/schematizingTreeView.ts @@ -183,7 +183,10 @@ export class SchematizingSimpleTreeView< prepareContentForHydration(mapTree, this.checkout.forest); initialize(this.checkout, { schema: toStoredSchema(this.viewSchema.schema), - initialTree: mapTree === undefined ? undefined : cursorForMapTreeNode(mapTree), + initialTree: + mapTree === undefined + ? undefined + : this.checkout.forest.chunkField(cursorForMapTreeNode(mapTree)), }); }); } diff --git a/packages/dds/tree/src/test/feature-libraries/flex-tree/utils.ts b/packages/dds/tree/src/test/feature-libraries/flex-tree/utils.ts index 50fdc6667358..881493dfd39f 100644 --- a/packages/dds/tree/src/test/feature-libraries/flex-tree/utils.ts +++ b/packages/dds/tree/src/test/feature-libraries/flex-tree/utils.ts @@ -8,7 +8,6 @@ import { strict as assert } from "node:assert"; import { type FieldAnchor, type IEditableForest, - type ITreeCursorSynchronous, type ITreeSubscriptionCursor, TreeNavigationResult, TreeStoredSchemaRepository, @@ -16,7 +15,11 @@ import { } from "../../../core/index.js"; // eslint-disable-next-line import/no-internal-modules import { type Context, getTreeContext } from "../../../feature-libraries/flex-tree/context.js"; -import { defaultSchemaPolicy, MockNodeKeyManager } from "../../../feature-libraries/index.js"; +import { + defaultSchemaPolicy, + MockNodeKeyManager, + type TreeChunk, +} from "../../../feature-libraries/index.js"; import { MockTreeCheckout, forestWithContent } from "../../utils.js"; import { toStoredSchema, @@ -58,7 +61,7 @@ export interface TreeSimpleContent { * Default tree content to initialize the tree with iff the tree is uninitialized * (meaning it does not even have any schema set at all). */ - readonly initialTree: readonly ITreeCursorSynchronous[] | ITreeCursorSynchronous | undefined; + readonly initialTree: TreeChunk | undefined; } /** diff --git a/packages/dds/tree/src/test/utils.ts b/packages/dds/tree/src/test/utils.ts index 7deb0d5689f7..dd7f4d1f6891 100644 --- a/packages/dds/tree/src/test/utils.ts +++ b/packages/dds/tree/src/test/utils.ts @@ -112,6 +112,7 @@ import { cursorForJsonableTreeField, initializeForest, chunkFieldSingle, + emptyChunk, } from "../feature-libraries/index.js"; // eslint-disable-next-line import/no-internal-modules import { makeSchemaCodec } from "../feature-libraries/schema-index/codec.js"; @@ -159,7 +160,7 @@ import { } from "../util/index.js"; import { isFluidHandle, toFluidHandleInternal } from "@fluidframework/runtime-utils/internal"; import type { Client } from "@fluid-private/test-dds-utils"; -import { cursorToJsonObject, fieldJsonCursor, singleJsonCursor } from "./json/index.js"; +import { cursorToJsonObject, fieldJsonCursor } from "./json/index.js"; // eslint-disable-next-line import/no-internal-modules import type { TreeSimpleContent } from "./feature-libraries/flex-tree/utils.js"; import type { Transactor } from "../shared-tree-core/index.js"; @@ -622,7 +623,7 @@ export function validateFuzzTreeConsistency( function contentToJsonableTree( content: TreeSimpleContent | TreeStoredContent, ): JsonableTree[] { - return jsonableTreeFromFieldCursor(normalizeNewFieldContent(content.initialTree)); + return jsonableTreeFromFieldCursor((content.initialTree ?? emptyChunk).cursor()); } export function validateTreeContent(tree: ITreeCheckout, content: TreeSimpleContent): void { @@ -794,7 +795,7 @@ export function flexTreeViewWithContent( export function forestWithContent(content: TreeStoredContent): IEditableForest { const forest = buildForest(); - const fieldCursor = normalizeNewFieldContent(content.initialTree); + const fieldCursor = (content.initialTree ?? emptyChunk).cursor(); initializeForest(forest, fieldCursor, testRevisionTagCodec, testIdCompressor); return forest; } @@ -814,7 +815,7 @@ export const IdentifierSchema = sf.object("identifier-object", { export function makeTreeFromJson(json: JsonCompatible): ITreeCheckout { return checkoutWithContent({ schema: toStoredSchema(JsonAsTree.Tree), - initialTree: singleJsonCursor(json), + initialTree: chunkFromJsonField([json]), }); } From 00a0f10d94f29869a7b68e781ec1ae6269986d76 Mon Sep 17 00:00:00 2001 From: Yann Achard Date: Thu, 6 Mar 2025 12:36:58 -0800 Subject: [PATCH 4/7] full build --- packages/dds/tree/src/core/tree/chunk.ts | 2 +- .../default-schema/defaultEditBuilder.ts | 48 +-- .../feature-libraries/default-schema/index.ts | 2 + .../default-schema/mappedEditBuilder.ts | 65 ++++ .../feature-libraries/flex-tree/lazyField.ts | 15 +- .../modular-schema/modularChangeFamily.ts | 27 +- .../tree/src/shared-tree/schematizeTree.ts | 8 +- .../src/shared-tree/schematizingTreeView.ts | 5 +- .../chunkEncodingEndToEnd.spec.ts | 8 +- .../defaultChangeFamily.spec.ts | 38 +-- .../test/feature-libraries/flex-tree/utils.ts | 9 +- .../modularChangeFamilyIntegration.spec.ts | 4 +- .../dds/tree/src/test/sequenceRootUtils.ts | 11 +- .../src/test/shared-tree-core/branch.spec.ts | 7 +- .../edit-manager/editManager.bench.ts | 5 +- .../shared-tree-core/sharedTreeCore.spec.ts | 18 +- .../test/shared-tree-core/transaction.spec.ts | 7 +- .../tree/src/test/shared-tree/editing.spec.ts | 309 ++++++------------ .../src/test/shared-tree/sharedTree.bench.ts | 6 +- .../src/test/shared-tree/sharedTree.spec.ts | 12 +- .../sharedTreeChangeFamily.spec.ts | 12 +- .../src/test/shared-tree/treeCheckout.spec.ts | 7 +- .../tree/src/test/shared-tree/undo.spec.ts | 19 +- .../test/simple-tree/api/treeNodeApi.spec.ts | 7 +- packages/dds/tree/src/test/utils.ts | 9 +- 25 files changed, 286 insertions(+), 374 deletions(-) create mode 100644 packages/dds/tree/src/feature-libraries/default-schema/mappedEditBuilder.ts diff --git a/packages/dds/tree/src/core/tree/chunk.ts b/packages/dds/tree/src/core/tree/chunk.ts index 8e531cd4a4e8..61072ad361b0 100644 --- a/packages/dds/tree/src/core/tree/chunk.ts +++ b/packages/dds/tree/src/core/tree/chunk.ts @@ -6,7 +6,7 @@ import { assert } from "@fluidframework/core-utils/internal"; import type { ReferenceCounted } from "../../util/index.js"; -import type { FieldKey, TreeNodeSchemaIdentifier } from "../schema-stored/index.js"; +import type { FieldKey } from "../schema-stored/index.js"; import { rootFieldKey } from "./types.js"; import { CursorLocationType, diff --git a/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts b/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts index f04c82be3d76..f9b2dc549363 100644 --- a/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts +++ b/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts @@ -13,13 +13,12 @@ import { type ChangeFamily, type ChangeFamilyEditor, type ChangeRebaser, - CursorLocationType, type DeltaDetachedNodeId, type DeltaRoot, type FieldUpPath, - type ITreeCursorSynchronous, type RevisionTag, type TaggedChange, + type TreeChunk, type UpPath, compareFieldUpPaths, topDownPath, @@ -120,14 +119,14 @@ export function relevantRemovedRoots(change: ModularChangeset): Iterable { /** * @param field - the value field which is being edited under the parent node * @returns An object with methods to edit the given field of the given parent. * The returned object can be used (i.e., have its methods called) multiple times but its lifetime * is bounded by the lifetime of this edit builder. */ - valueField(field: FieldUpPath): ValueFieldEditBuilder; + valueField(field: FieldUpPath): ValueFieldEditBuilder; /** * @param field - the optional field which is being edited under the parent node @@ -135,7 +134,7 @@ export interface IDefaultEditBuilder { * The returned object can be used (i.e., have its methods called) multiple times but its lifetime * is bounded by the lifetime of this edit builder. */ - optionalField(field: FieldUpPath): OptionalFieldEditBuilder; + optionalField(field: FieldUpPath): OptionalFieldEditBuilder; /** * @param field - the sequence field which is being edited under the parent node @@ -144,7 +143,7 @@ export interface IDefaultEditBuilder { * The returned object can be used (i.e., have its methods called) multiple times but its lifetime * is bounded by the lifetime of this edit builder. */ - sequenceField(field: FieldUpPath): SequenceFieldEditBuilder; + sequenceField(field: FieldUpPath): SequenceFieldEditBuilder; /** * Moves a subsequence from one sequence field to another sequence field. @@ -204,18 +203,13 @@ export class DefaultEditBuilder implements ChangeFamilyEditor, IDefaultEditBuild this.modularBuilder.addNodeExistsConstraintOnRevert(path, this.mintRevisionTag()); } - public valueField(field: FieldUpPath): ValueFieldEditBuilder { + public valueField(field: FieldUpPath): ValueFieldEditBuilder { return { - set: (newContent: ITreeCursorSynchronous): void => { + set: (newContent: TreeChunk): void => { const revision = this.mintRevisionTag(); const fill: ChangeAtomId = { localId: this.modularBuilder.generateId(), revision }; const detach: ChangeAtomId = { localId: this.modularBuilder.generateId(), revision }; - const build = this.modularBuilder.buildTrees( - fill.localId, - newContent, - revision, - this.idCompressor, - ); + const build = this.modularBuilder.buildTrees(fill.localId, newContent, revision); const change: FieldChangeset = brand( valueFieldKind.changeHandler.editor.set({ fill, @@ -235,21 +229,16 @@ export class DefaultEditBuilder implements ChangeFamilyEditor, IDefaultEditBuild }; } - public optionalField(field: FieldUpPath): OptionalFieldEditBuilder { + public optionalField(field: FieldUpPath): OptionalFieldEditBuilder { return { - set: (newContent: ITreeCursorSynchronous | undefined, wasEmpty: boolean): void => { + set: (newContent: TreeChunk | undefined, wasEmpty: boolean): void => { const edits: EditDescription[] = []; let optionalChange: OptionalChangeset; const revision = this.mintRevisionTag(); const detach: ChangeAtomId = { localId: this.modularBuilder.generateId(), revision }; if (newContent !== undefined) { const fill: ChangeAtomId = { localId: this.modularBuilder.generateId(), revision }; - const build = this.modularBuilder.buildTrees( - fill.localId, - newContent, - revision, - this.idCompressor, - ); + const build = this.modularBuilder.buildTrees(fill.localId, newContent, revision); edits.push(build); optionalChange = optional.changeHandler.editor.set(wasEmpty, { @@ -381,23 +370,16 @@ export class DefaultEditBuilder implements ChangeFamilyEditor, IDefaultEditBuild } } - public sequenceField(field: FieldUpPath): SequenceFieldEditBuilder { + public sequenceField(field: FieldUpPath): SequenceFieldEditBuilder { return { - insert: (index: number, content: ITreeCursorSynchronous): void => { - const length = - content.mode === CursorLocationType.Fields ? content.getFieldLength() : 1; - if (length === 0) { + insert: (index: number, content: TreeChunk): void => { + if (content.topLevelLength === 0) { return; } const revision = this.mintRevisionTag(); const firstId: CellId = { localId: this.modularBuilder.generateId(length), revision }; - const build = this.modularBuilder.buildTrees( - firstId.localId, - content, - revision, - this.idCompressor, - ); + const build = this.modularBuilder.buildTrees(firstId.localId, content, revision); const change: FieldChangeset = brand( sequence.changeHandler.editor.insert(index, length, firstId, revision), ); diff --git a/packages/dds/tree/src/feature-libraries/default-schema/index.ts b/packages/dds/tree/src/feature-libraries/default-schema/index.ts index 0ec7c5b9ff41..39731d21ebb8 100644 --- a/packages/dds/tree/src/feature-libraries/default-schema/index.ts +++ b/packages/dds/tree/src/feature-libraries/default-schema/index.ts @@ -29,3 +29,5 @@ export { export { SchemaValidationErrors, isNodeInSchema, isFieldInSchema } from "./schemaChecker.js"; export { defaultSchemaPolicy } from "./defaultSchema.js"; + +export { MappedEditBuilder } from "./mappedEditBuilder.js"; diff --git a/packages/dds/tree/src/feature-libraries/default-schema/mappedEditBuilder.ts b/packages/dds/tree/src/feature-libraries/default-schema/mappedEditBuilder.ts new file mode 100644 index 000000000000..78ec532e8e4f --- /dev/null +++ b/packages/dds/tree/src/feature-libraries/default-schema/mappedEditBuilder.ts @@ -0,0 +1,65 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { FieldUpPath, UpPath } from "../../core/index.js"; +import type { + IDefaultEditBuilder, + OptionalFieldEditBuilder, + SequenceFieldEditBuilder, + ValueFieldEditBuilder, +} from "./defaultEditBuilder.js"; + +export class MappedEditBuilder implements IDefaultEditBuilder { + public constructor( + private readonly baseBuilder: IDefaultEditBuilder, + private readonly mapDelegate: (input: TAdapted) => TBase, + ) {} + public valueField(field: FieldUpPath): ValueFieldEditBuilder { + const baseField = this.baseBuilder.valueField(field); + return { + set: (newContent: TAdapted): void => { + const mappedContent = this.mapDelegate(newContent); + baseField.set(mappedContent); + }, + }; + } + public optionalField(field: FieldUpPath): OptionalFieldEditBuilder { + const baseField = this.baseBuilder.optionalField(field); + return { + set: (newContent: TAdapted | undefined, wasEmpty: boolean): void => { + const mappedContent = + newContent === undefined ? undefined : this.mapDelegate(newContent); + baseField.set(mappedContent, wasEmpty); + }, + }; + } + public sequenceField(field: FieldUpPath): SequenceFieldEditBuilder { + const baseField = this.baseBuilder.sequenceField(field); + return { + insert: (index: number, content: TAdapted): void => { + const mappedContent = this.mapDelegate(content); + baseField.insert(index, mappedContent); + }, + remove: (index: number, count: number): void => { + baseField.remove(index, count); + }, + }; + } + public move( + sourceField: FieldUpPath, + sourceIndex: number, + count: number, + destinationField: FieldUpPath, + destinationIndex: number, + ): void { + this.baseBuilder.move(sourceField, sourceIndex, count, destinationField, destinationIndex); + } + public addNodeExistsConstraint(path: UpPath): void { + this.baseBuilder.addNodeExistsConstraint(path); + } + public addNodeExistsConstraintOnRevert(path: UpPath): void { + this.baseBuilder.addNodeExistsConstraintOnRevert(path); + } +} diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts b/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts index 51f1fb2783ef..c8fcc139b0ba 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts @@ -24,6 +24,8 @@ import { import { disposeSymbol, fail, getOrCreate } from "../../util/index.js"; import { FieldKinds, + MappedEditBuilder, + type IDefaultEditBuilder, type OptionalFieldEditBuilder, type SequenceFieldEditBuilder, type ValueFieldEditBuilder, @@ -247,6 +249,13 @@ export abstract class LazyField extends LazyEntity implements FlexT throw new UsageError("Editing only allowed on fields with TreeStatus.InDocument status"); } + + protected getEditor(): IDefaultEditBuilder { + return new MappedEditBuilder( + this.context.checkout.editor, + (cursor: ITreeCursorSynchronous) => this.context.checkout.forest.chunkField(cursor), + ); + } } export class LazySequence extends LazyField implements FlexTreeSequenceField { @@ -274,7 +283,7 @@ export class LazySequence extends LazyField implements FlexTreeSequenceField { private sequenceEditor(): SequenceFieldEditBuilder { const fieldPath = this.getFieldPathForEditing(); - return this.context.checkout.editor.sequenceField(fieldPath); + return this.getEditor().sequenceField(fieldPath); } } @@ -299,7 +308,7 @@ export class LazyValueField extends ReadonlyLazyValueField implements FlexTreeRe private valueFieldEditor(): ValueFieldEditBuilder { const fieldPath = this.getFieldPathForEditing(); - const fieldEditor = this.context.checkout.editor.valueField(fieldPath); + const fieldEditor = this.getEditor().valueField(fieldPath); return fieldEditor; } @@ -320,7 +329,7 @@ export class LazyOptionalField extends LazyField implements FlexTreeOptionalFiel private optionalEditor(): OptionalFieldEditBuilder { const fieldPath = this.getFieldPathForEditing(); - const fieldEditor = this.context.checkout.editor.optionalField(fieldPath); + const fieldEditor = this.getEditor().optionalField(fieldPath); return fieldEditor; } diff --git a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts index 0b1963876e9e..3bc5269daffc 100644 --- a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts +++ b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts @@ -13,7 +13,6 @@ import { type ChangeFamilyEditor, type ChangeRebaser, type ChangesetLocalId, - CursorLocationType, type DeltaDetachedNodeBuild, type DeltaDetachedNodeDestruction, type DeltaDetachedNodeId, @@ -24,7 +23,6 @@ import { type FieldKey, type FieldKindIdentifier, type FieldUpPath, - type ITreeCursorSynchronous, type RevisionInfo, type RevisionMetadataSource, type RevisionTag, @@ -58,12 +56,7 @@ import { RangeMap, balancedReduce, } from "../../util/index.js"; -import { - type TreeChunk, - chunkFieldSingle, - chunkTree, - defaultChunkPolicy, -} from "../chunked-forest/index.js"; +import type { TreeChunk } from "../chunked-forest/index.js"; import { type CrossFieldManager, @@ -94,7 +87,6 @@ import { type NodeChangeset, type NodeId, } from "./modularChangeTypes.js"; -import type { IIdCompressor } from "@fluidframework/id-compressor"; /** * Implementation of ChangeFamily which delegates work in a given field to the appropriate FieldKind @@ -2660,29 +2652,20 @@ export class ModularEditBuilder extends EditBuilder { /** * @param firstId - The ID to associate with the first node - * @param content - The node(s) to build. Can be in either Field or Node mode. + * @param content - The node(s) to build. * @param revision - The revision to use for the build. * @returns A description of the edit that can be passed to `submitChanges`. */ public buildTrees( firstId: ChangesetLocalId, - content: ITreeCursorSynchronous, + content: TreeChunk, revision: RevisionTag, - idCompressor?: IIdCompressor, ): GlobalEditDescription { - if (content.mode === CursorLocationType.Fields && content.getFieldLength() === 0) { + if (content.topLevelLength === 0) { return { type: "global", revision }; } const builds: ChangeAtomIdBTree = newTupleBTree(); - const chunkCompressor = { - policy: defaultChunkPolicy, - idCompressor, - }; - const chunk = - content.mode === CursorLocationType.Fields - ? chunkFieldSingle(content, chunkCompressor) - : chunkTree(content, chunkCompressor); - builds.set([revision, firstId], chunk); + builds.set([revision, firstId], content); return { type: "global", diff --git a/packages/dds/tree/src/shared-tree/schematizeTree.ts b/packages/dds/tree/src/shared-tree/schematizeTree.ts index 3fd13bc38b4d..9c5b35176a49 100644 --- a/packages/dds/tree/src/shared-tree/schematizeTree.ts +++ b/packages/dds/tree/src/shared-tree/schematizeTree.ts @@ -19,7 +19,6 @@ import { cursorForMapTreeField, defaultSchemaPolicy, mapTreeFromCursor, - type TreeChunk, } from "../feature-libraries/index.js"; import { fail, isReadonlyArray } from "../util/index.js"; @@ -181,6 +180,7 @@ export function initialize(checkout: ITreeCheckout, treeContent: TreeStoredConte initializeContent(checkout, treeContent.schema, () => { const field = { field: rootFieldKey, parent: undefined }; const content = normalizeNewFieldContent(treeContent.initialTree); + const contentChunk = checkout.forest.chunkField(content); switch (checkout.storedSchema.rootFieldSchema.kind) { case FieldKinds.optional.identifier: { @@ -189,13 +189,13 @@ export function initialize(checkout: ITreeCheckout, treeContent: TreeStoredConte content.getFieldLength() <= 1, 0x7f4 /* optional field content should normalize at most one item */, ); - fieldEditor.set(content.getFieldLength() === 0 ? undefined : content, true); + fieldEditor.set(contentChunk.topLevelLength === 0 ? undefined : contentChunk, true); break; } case FieldKinds.sequence.identifier: { const fieldEditor = checkout.editor.sequenceField(field); // TODO: should do an idempotent edit here. - fieldEditor.insert(0, content); + fieldEditor.insert(0, contentChunk); break; } default: { @@ -270,5 +270,5 @@ export interface TreeStoredContent { * Default tree content to initialize the tree with iff the tree is uninitialized * (meaning it does not even have any schema set at all). */ - readonly initialTree: TreeChunk | undefined; + readonly initialTree: readonly ITreeCursorSynchronous[] | ITreeCursorSynchronous | undefined; } diff --git a/packages/dds/tree/src/shared-tree/schematizingTreeView.ts b/packages/dds/tree/src/shared-tree/schematizingTreeView.ts index 9496c4252f65..156fb08fb7ed 100644 --- a/packages/dds/tree/src/shared-tree/schematizingTreeView.ts +++ b/packages/dds/tree/src/shared-tree/schematizingTreeView.ts @@ -183,10 +183,7 @@ export class SchematizingSimpleTreeView< prepareContentForHydration(mapTree, this.checkout.forest); initialize(this.checkout, { schema: toStoredSchema(this.viewSchema.schema), - initialTree: - mapTree === undefined - ? undefined - : this.checkout.forest.chunkField(cursorForMapTreeNode(mapTree)), + initialTree: mapTree === undefined ? undefined : cursorForMapTreeNode(mapTree), }); }); } diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts index 98684a8a2c21..b817dffe0abb 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkEncodingEndToEnd.spec.ts @@ -166,9 +166,7 @@ describe("End to end chunked encoding", () => { const checkout = new MockTreeCheckout(forest, { editor: dummyEditor as unknown as ISharedTreeEditor, }); - checkout.editor - .sequenceField({ field: rootFieldKey, parent: undefined }) - .insert(0, chunk.cursor()); + checkout.editor.sequenceField({ field: rootFieldKey, parent: undefined }).insert(0, chunk); // Check that inserted change contains chunk which is reference equal to the original chunk. const { change: insertedChange, revision } = changeLog[0]; assert(insertedChange.builds !== undefined); @@ -188,9 +186,7 @@ describe("End to end chunked encoding", () => { initialTree: [], }); - checkout.editor - .sequenceField({ field: rootFieldKey, parent: undefined }) - .insert(0, chunk.cursor()); + checkout.editor.sequenceField({ field: rootFieldKey, parent: undefined }).insert(0, chunk); const forestSummarizer = new ForestSummarizer( checkout.forest, diff --git a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts index ccc7691b7d6f..b879efaef31b 100644 --- a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts @@ -7,7 +7,6 @@ import { strict as assert } from "node:assert"; import { type DeltaRoot, - EmptyKey, type FieldKey, type IForestSubscription, type JsonableTree, @@ -25,7 +24,6 @@ import { DefaultEditBuilder, buildForest, cursorForJsonableTreeField, - cursorForJsonableTreeNode, initializeForest, intoDelta, jsonableTreeFromCursor, @@ -33,6 +31,7 @@ import { import { brand } from "../../../util/index.js"; import { assertDeltaEqual, + chunkFromJsonableField, failCodecFamily, mintRevisionTag, testIdCompressor, @@ -97,6 +96,7 @@ const root_bar0_bar0: UpPath = { }; const nodeX: JsonableTree = { type: brand(stringSchema.identifier), value: "X" }; +const nodeXChunk = chunkFromJsonableField([nodeX]); function assertDeltasEqual(actual: DeltaRoot[], expected: DeltaRoot[]): void { assert.equal(actual.length, expected.length); @@ -177,7 +177,7 @@ describe("DefaultEditBuilder", () => { assert.equal(deltas.length, 1); fooEditor.insert( 0, - cursorForJsonableTreeNode({ type: brand(numberSchema.identifier), value: 42 }), + chunkFromJsonableField([{ type: brand(numberSchema.identifier), value: 42 }]), ); expectForest(forest, { type: brand(JsonAsTree.JsonObject.identifier), @@ -196,9 +196,7 @@ describe("DefaultEditBuilder", () => { const { builder, forest } = initializeEditableForest({ type: brand(JsonAsTree.JsonObject.identifier), }); - builder - .valueField({ parent: undefined, field: rootKey }) - .set(cursorForJsonableTreeNode(nodeX)); + builder.valueField({ parent: undefined, field: rootKey }).set(nodeXChunk); expectForest(forest, nodeX); }); @@ -218,9 +216,7 @@ describe("DefaultEditBuilder", () => { ], }, }); - builder - .valueField({ parent: root_foo2, field: fooKey }) - .set(cursorForJsonableTreeNode(nodeX)); + builder.valueField({ parent: root_foo2, field: fooKey }).set(nodeXChunk); const expected: JsonableTree = { type: brand(JsonAsTree.JsonObject.identifier), fields: { @@ -245,9 +241,7 @@ describe("DefaultEditBuilder", () => { const { builder, forest } = initializeEditableForest({ type: brand(JsonAsTree.JsonObject.identifier), }); - builder - .optionalField({ parent: undefined, field: rootKey }) - .set(cursorForJsonableTreeNode(nodeX), false); + builder.optionalField({ parent: undefined, field: rootKey }).set(nodeXChunk, false); expectForest(forest, nodeX); }); @@ -267,9 +261,7 @@ describe("DefaultEditBuilder", () => { ], }, }); - builder - .optionalField({ parent: root_foo2, field: fooKey }) - .set(cursorForJsonableTreeNode(nodeX), false); + builder.optionalField({ parent: root_foo2, field: fooKey }).set(nodeXChunk, false); const expected: JsonableTree = { type: brand(JsonAsTree.JsonObject.identifier), fields: { @@ -290,9 +282,7 @@ describe("DefaultEditBuilder", () => { it("Can set an empty root field", () => { const { builder, forest } = initializeEditableForest(); - builder - .optionalField({ parent: undefined, field: rootKey }) - .set(cursorForJsonableTreeNode(nodeX), true); + builder.optionalField({ parent: undefined, field: rootKey }).set(nodeXChunk, true); expectForest(forest, nodeX); }); @@ -307,9 +297,7 @@ describe("DefaultEditBuilder", () => { ], }, }); - builder - .optionalField({ parent: root_foo2, field: fooKey }) - .set(cursorForJsonableTreeNode(nodeX), true); + builder.optionalField({ parent: root_foo2, field: fooKey }).set(nodeXChunk, true); const expected: JsonableTree = { type: brand(JsonAsTree.JsonObject.identifier), fields: { @@ -327,9 +315,7 @@ describe("DefaultEditBuilder", () => { describe("Sequence Field Edits", () => { it("Can insert a root node", () => { const { builder, forest } = initializeEditableForest(); - builder - .sequenceField({ parent: undefined, field: rootKey }) - .insert(0, cursorForJsonableTreeNode(nodeX)); + builder.sequenceField({ parent: undefined, field: rootKey }).insert(0, nodeXChunk); expectForest(forest, nodeX); }); @@ -355,9 +341,7 @@ describe("DefaultEditBuilder", () => { ], }, }); - builder - .sequenceField({ parent: root_foo2, field: fooKey }) - .insert(5, cursorForJsonableTreeNode(nodeX)); + builder.sequenceField({ parent: root_foo2, field: fooKey }).insert(5, nodeXChunk); const expected: JsonableTree = { type: brand(JsonAsTree.JsonObject.identifier), fields: { diff --git a/packages/dds/tree/src/test/feature-libraries/flex-tree/utils.ts b/packages/dds/tree/src/test/feature-libraries/flex-tree/utils.ts index 881493dfd39f..50fdc6667358 100644 --- a/packages/dds/tree/src/test/feature-libraries/flex-tree/utils.ts +++ b/packages/dds/tree/src/test/feature-libraries/flex-tree/utils.ts @@ -8,6 +8,7 @@ import { strict as assert } from "node:assert"; import { type FieldAnchor, type IEditableForest, + type ITreeCursorSynchronous, type ITreeSubscriptionCursor, TreeNavigationResult, TreeStoredSchemaRepository, @@ -15,11 +16,7 @@ import { } from "../../../core/index.js"; // eslint-disable-next-line import/no-internal-modules import { type Context, getTreeContext } from "../../../feature-libraries/flex-tree/context.js"; -import { - defaultSchemaPolicy, - MockNodeKeyManager, - type TreeChunk, -} from "../../../feature-libraries/index.js"; +import { defaultSchemaPolicy, MockNodeKeyManager } from "../../../feature-libraries/index.js"; import { MockTreeCheckout, forestWithContent } from "../../utils.js"; import { toStoredSchema, @@ -61,7 +58,7 @@ export interface TreeSimpleContent { * Default tree content to initialize the tree with iff the tree is uninitialized * (meaning it does not even have any schema set at all). */ - readonly initialTree: TreeChunk | undefined; + readonly initialTree: readonly ITreeCursorSynchronous[] | ITreeCursorSynchronous | undefined; } /** diff --git a/packages/dds/tree/src/test/feature-libraries/modularChangeFamilyIntegration.spec.ts b/packages/dds/tree/src/test/feature-libraries/modularChangeFamilyIntegration.spec.ts index 0a661c328f03..a2a9b5e67156 100644 --- a/packages/dds/tree/src/test/feature-libraries/modularChangeFamilyIntegration.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modularChangeFamilyIntegration.spec.ts @@ -607,7 +607,7 @@ describe("ModularChangeFamily integration", () => { parent: { parent: undefined, parentField: fieldB, parentIndex: 0 }, field: fieldC, }) - .insert(0, newNode.cursor()); + .insert(0, newNode); const [move, insert] = getChanges(); const composed = family.compose([makeAnonChange(move), makeAnonChange(insert)]); @@ -654,7 +654,7 @@ describe("ModularChangeFamily integration", () => { parent: { parent: undefined, parentField: fieldB, parentIndex: 0 }, field: fieldC, }) - .insert(0, newNode.cursor()); + .insert(0, newNode); const [move, insert] = getChanges(); const moveTagged = tagChangeInline(move, tag1); diff --git a/packages/dds/tree/src/test/sequenceRootUtils.ts b/packages/dds/tree/src/test/sequenceRootUtils.ts index 35981192db13..9c748b7ef298 100644 --- a/packages/dds/tree/src/test/sequenceRootUtils.ts +++ b/packages/dds/tree/src/test/sequenceRootUtils.ts @@ -6,14 +6,14 @@ import { type TreeStoredSchema, rootFieldKey, - type MapTree, type TreeNodeSchemaIdentifier, + type JsonableTree, } from "../core/index.js"; -import { FieldKinds, cursorForMapTreeField } from "../feature-libraries/index.js"; +import { FieldKinds } from "../feature-libraries/index.js"; import type { ITreeCheckout } from "../shared-tree/index.js"; import { stringSchema, toStoredSchema } from "../simple-tree/index.js"; import { brand, type JsonCompatible } from "../util/index.js"; -import { checkoutWithContent } from "./utils.js"; +import { checkoutWithContent, chunkFromJsonableField } from "./utils.js"; // eslint-disable-next-line import/no-internal-modules import { normalizeAllowedTypes } from "../simple-tree/schemaTypes.js"; import { singleJsonCursor } from "./json/index.js"; @@ -45,10 +45,9 @@ export function insert(tree: ITreeCheckout, index: number, ...values: string[]): const fieldEditor = tree.editor.sequenceField({ field: rootFieldKey, parent: undefined }); fieldEditor.insert( index, - cursorForMapTreeField( + chunkFromJsonableField( values.map( - (value): MapTree => ({ - fields: new Map(), + (value): JsonableTree => ({ type: brand(stringSchema.identifier), value, }), diff --git a/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts b/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts index 66efec911eb2..42331f064ff0 100644 --- a/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts @@ -18,7 +18,6 @@ import { DefaultChangeFamily, type DefaultChangeset, type DefaultEditBuilder, - cursorForJsonableTreeNode, } from "../../feature-libraries/index.js"; import { SharedTreeBranch, @@ -26,7 +25,7 @@ import { onForkTransitive, } from "../../shared-tree-core/index.js"; import { brand, fail } from "../../util/index.js"; -import { failCodecFamily, mintRevisionTag } from "../utils.js"; +import { chunkFromJsonableField, failCodecFamily, mintRevisionTag } from "../utils.js"; const defaultChangeFamily = new DefaultChangeFamily(failCodecFamily); @@ -395,8 +394,8 @@ describe("Branches", () => { /** Apply an arbitrary but unique change to the given branch and return the tag for the new commit */ function change(branch: DefaultBranch): RevisionTag { - const cursor = cursorForJsonableTreeNode({ type: brand("TestValue"), value: changeValue }); - branch.editor.valueField({ parent: undefined, field: rootFieldKey }).set(cursor); + const content = chunkFromJsonableField([{ type: brand("TestValue"), value: changeValue }]); + branch.editor.valueField({ parent: undefined, field: rootFieldKey }).set(content); return branch.getHead().revision; } diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts index 455e5bdb1d4b..1844ddedfd85 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts @@ -18,7 +18,7 @@ import type { Commit } from "../../../shared-tree-core/index.js"; import { brand } from "../../../util/index.js"; import { type Editor, makeEditMinter } from "../../editMinter.js"; import { NoOpChangeRebaser, TestChange, testChangeFamilyFactory } from "../../testChange.js"; -import { failCodecFamily, mintRevisionTag } from "../../utils.js"; +import { chunkFromJsonField, failCodecFamily, mintRevisionTag } from "../../utils.js"; import { editManagerFactory, @@ -27,7 +27,6 @@ import { rebaseLocalEditsOverTrunkEdits, rebasePeerEditsOverTrunkEdits, } from "./editManagerTestUtils.js"; -import { singleJsonCursor } from "../../json/index.js"; describe("EditManager - Bench", () => { interface Scenario { @@ -58,7 +57,7 @@ describe("EditManager - Bench", () => { const sequencePrepend: Editor = (builder) => { builder .sequenceField({ parent: undefined, field: rootFieldKey }) - .insert(0, singleJsonCursor(1)); + .insert(0, chunkFromJsonField([1])); }; // Family is invariant over the change type, so using any is required to write generic Family processing code. diff --git a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts index ab60f2b0a987..1f9a3a3ce7c9 100644 --- a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts @@ -34,11 +34,10 @@ import { type GraphCommit, rootFieldKey, } from "../../core/index.js"; -import { - type DefaultChangeset, - type DefaultEditBuilder, - type ModularChangeset, - cursorForJsonableTreeNode, +import type { + DefaultChangeset, + DefaultEditBuilder, + ModularChangeset, } from "../../feature-libraries/index.js"; import { Tree } from "../../shared-tree/index.js"; import type { @@ -51,7 +50,12 @@ import type { SummaryElementStringifier, } from "../../shared-tree-core/index.js"; import { brand, disposeSymbol } from "../../util/index.js"; -import { SharedTreeTestFactory, StringArray, TestTreeProviderLite } from "../utils.js"; +import { + chunkFromJsonableField, + SharedTreeTestFactory, + StringArray, + TestTreeProviderLite, +} from "../utils.js"; import { createTree, createTreeSharedObject, TestSharedTreeCore } from "./utils.js"; import { SchemaFactory, TreeViewConfiguration } from "../../simple-tree/index.js"; @@ -649,7 +653,7 @@ function changeTree( tree: SharedTreeCore, ): void { const field = tree.getEditor().sequenceField({ parent: undefined, field: rootFieldKey }); - field.insert(0, cursorForJsonableTreeNode({ type: brand("Node"), value: 42 })); + field.insert(0, chunkFromJsonableField([{ type: brand("Node"), value: 42 }])); } /** Returns the length of the trunk branch in the given tree. Acquired via unholy cast; use for glass-box tests only. */ diff --git a/packages/dds/tree/src/test/shared-tree-core/transaction.spec.ts b/packages/dds/tree/src/test/shared-tree-core/transaction.spec.ts index 9f9c4f842d01..dabf8d758351 100644 --- a/packages/dds/tree/src/test/shared-tree-core/transaction.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/transaction.spec.ts @@ -12,12 +12,11 @@ import { } from "../../shared-tree-core/index.js"; import { validateAssertionError } from "@fluidframework/test-runtime-utils/internal"; import { - cursorForJsonableTreeNode, DefaultChangeFamily, type DefaultChangeset, type DefaultEditBuilder, } from "../../feature-libraries/index.js"; -import { failCodecFamily, mintRevisionTag } from "../utils.js"; +import { chunkFromJsonableField, failCodecFamily, mintRevisionTag } from "../utils.js"; import { findAncestor, rootFieldKey, @@ -274,8 +273,8 @@ describe("SquashingTransactionStacks", () => { } function edit(editor: DefaultEditBuilder, value: string): void { - const cursor = cursorForJsonableTreeNode({ type: brand("TestValue"), value }); - editor.valueField({ parent: undefined, field: rootFieldKey }).set(cursor); + const content = chunkFromJsonableField([{ type: brand("TestValue"), value }]); + editor.valueField({ parent: undefined, field: rootFieldKey }).set(content); } function squash(commits: GraphCommit[]): TaggedChange { diff --git a/packages/dds/tree/src/test/shared-tree/editing.spec.ts b/packages/dds/tree/src/test/shared-tree/editing.spec.ts index ed185ce13d8e..596a2e184f5a 100644 --- a/packages/dds/tree/src/test/shared-tree/editing.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/editing.spec.ts @@ -15,11 +15,12 @@ import { moveToDetachedField, rootFieldKey, } from "../../core/index.js"; -import { cursorForJsonableTreeNode } from "../../feature-libraries/index.js"; import type { ITreeCheckout, TreeStoredContent } from "../../shared-tree/index.js"; import { type JsonCompatible, brand, fail, makeArray } from "../../util/index.js"; import { checkoutWithContent, + chunkFromJsonableField, + chunkFromJsonField, createTestUndoRedoStacks, expectJsonTree, expectNoRemovedRoots, @@ -28,8 +29,7 @@ import { validateUsageError, } from "../utils.js"; import { insert, makeTreeFromJsonSequence, remove } from "../sequenceRootUtils.js"; -import { SchemaFactory, stringSchema, toStoredSchema } from "../../simple-tree/index.js"; -import { singleJsonCursor } from "../json/index.js"; +import { SchemaFactory, toStoredSchema } from "../../simple-tree/index.js"; import { JsonAsTree } from "../../jsonDomainSchema.js"; const rootField: FieldUpPath = { @@ -261,13 +261,10 @@ describe("Editing", () => { const tree3 = tree1.branch(); insert(tree2, 1, "C"); - tree3.editor.sequenceField(rootField).insert(0, singleJsonCursor({})); + tree3.editor.sequenceField(rootField).insert(0, chunkFromJsonField([{}])); const aEditor = tree3.editor.sequenceField({ parent: rootNode, field: brand("foo") }); - aEditor.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "a" }), - ); + aEditor.insert(0, chunkFromJsonField(["a"])); tree1.merge(tree2, false); tree1.merge(tree3, false); @@ -363,10 +360,7 @@ describe("Editing", () => { const fooListPath: FieldUpPath = { parent: fooList, field: brand("") }; const listEditor = tree2.editor.sequenceField(fooListPath); moveWithin(tree2.editor, fooListPath, 2, 1, 1); - listEditor.insert( - 3, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "D" }), - ); + listEditor.insert(3, chunkFromJsonField(["D"])); listEditor.remove(0, 1); expectJsonTree(tree2, [{ foo: ["C", "B", "D"] }]); @@ -520,10 +514,10 @@ describe("Editing", () => { insert(tree1, 0, "a"); expectJsonTree(tree1, ["a"]); - tree2.editor.sequenceField(rootField).insert(0, singleJsonCursor({})); + tree2.editor.sequenceField(rootField).insert(0, chunkFromJsonField(["{}"])); tree2.editor .sequenceField({ parent: rootNode, field: brand("foo") }) - .insert(0, singleJsonCursor({})); + .insert(0, chunkFromJsonField(["{}"])); expectJsonTree(tree2, [{ foo: {} }]); tree2.rebaseOnto(tree1); @@ -552,9 +546,7 @@ describe("Editing", () => { const parent = { parent: undefined, parentField: rootFieldKey, parentIndex: 1 }; const editor = tree1.editor.valueField({ parent, field: brand("foo") }); - editor.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "C" }), - ); + editor.set(chunkFromJsonField(["C"])); // Move B before A. tree2.editor.move(rootField, 1, 1, rootField, 0); @@ -582,9 +574,7 @@ describe("Editing", () => { }, field: brand("foo"), }); - editor.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "C" }), - ); + editor.set(chunkFromJsonField(["C"])); tree1.merge(tree2, false); tree2.rebaseOnto(tree1); @@ -606,7 +596,7 @@ describe("Editing", () => { }; tree1.editor.move(seqField, 0, 1, seqField, 1); - tree2.editor.valueField(fooField).set(singleJsonCursor("a")); + tree2.editor.valueField(fooField).set(chunkFromJsonField(["a"])); tree2.rebaseOnto(tree1); tree1.merge(tree2, false); @@ -631,9 +621,7 @@ describe("Editing", () => { parent: { parent: fooList, parentField: brand(""), parentIndex: 0 }, field: brand("baz"), }); - editor.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "C" }), - ); + editor.set(chunkFromJsonField(["C"])); // Move object from foo list to bar list tree2.editor.move( @@ -986,17 +974,11 @@ describe("Editing", () => { undoStack.pop()?.revert(); tree2.editor .sequenceField({ parent: sequenceUpPath, field: EmptyKey }) - .insert( - 1, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "c" }), - ); + .insert(1, chunkFromJsonField(["c"])); tree.editor .sequenceField({ parent: sequenceUpPath, field: EmptyKey }) - .insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "a" }), - ); + .insert(0, chunkFromJsonField(["a"])); tree2.rebaseOnto(tree); @@ -1029,17 +1011,11 @@ describe("Editing", () => { undoStack.pop()?.revert(); tree.editor .sequenceField({ parent: sequenceUpPath, field: EmptyKey }) - .insert( - 1, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "c" }), - ); + .insert(1, chunkFromJsonField(["c"])); tree2.editor .sequenceField({ parent: sequenceUpPath, field: EmptyKey }) - .insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "a" }), - ); + .insert(0, chunkFromJsonField(["a"])); tree2.rebaseOnto(tree); @@ -1296,18 +1272,9 @@ describe("Editing", () => { tree.transaction.start(); // inserts nodes to move const field = tree.editor.sequenceField({ parent: fooList, field: brand("") }); - field.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "C" }), - ); - field.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "B" }), - ); - field.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "A" }), - ); + field.insert(0, chunkFromJsonField(["C"])); + field.insert(0, chunkFromJsonField(["B"])); + field.insert(0, chunkFromJsonField(["A"])); // Move nodes from foo into bar. tree.editor.move( { parent: fooList, field: brand("") }, @@ -1599,7 +1566,7 @@ describe("Editing", () => { parent: rootNode, field: brand("src"), }); - field.set(singleJsonCursor({})); + field.set(chunkFromJsonField(["{}"])); tree.transaction.commit(); const expectedState: JsonCompatible = [{ src: {}, dst: ["A", "B"] }]; @@ -1735,7 +1702,7 @@ describe("Editing", () => { tree1.editor .valueField({ parent: nodeB, field: brand("baz") }) - .set(cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "b" })); + .set(chunkFromJsonField(["b"])); tree2.editor.sequenceField({ parent: foo1, field: brand("bar") }).remove(0, 1); tree.merge(tree1, false); @@ -2172,15 +2139,11 @@ describe("Editing", () => { tree2.editor .valueField({ parent: rootNode, field: brand("foo") }) - .set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "2" }), - ); + .set(chunkFromJsonField(["2"])); tree3.editor .valueField({ parent: rootNode, field: brand("foo") }) - .set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "3" }), - ); + .set(chunkFromJsonField(["3"])); tree1.merge(tree2, false); tree1.merge(tree3, false); @@ -2197,17 +2160,11 @@ describe("Editing", () => { tree2.editor .optionalField({ parent: rootNode, field: brand("foo") }) - .set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "2" }), - true, - ); + .set(chunkFromJsonField(["2"]), true); tree3.editor .optionalField({ parent: rootNode, field: brand("foo") }) - .set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "3" }), - true, - ); + .set(chunkFromJsonField(["3"]), true); tree3.rebaseOnto(tree2); tree2.merge(tree3, false); @@ -2221,17 +2178,15 @@ describe("Editing", () => { const tree1 = checkoutWithContent(emptyJsonContent); const tree2 = tree1.branch(); - tree1.editor.optionalField(rootField).set(singleJsonCursor("41"), true); + tree1.editor.optionalField(rootField).set(chunkFromJsonField(["41"]), true); - tree2.editor.optionalField(rootField).set(singleJsonCursor({ foo: "42" }), true); + tree2.editor.optionalField(rootField).set(chunkFromJsonField([{ foo: "42" }]), true); expectJsonTree([tree1], ["41"]); expectJsonTree([tree2], [{ foo: "42" }]); const editor = tree2.editor.valueField({ parent: rootNode, field: brand("foo") }); - editor.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "43" }), - ); + editor.set(chunkFromJsonField(["43"])); expectJsonTree([tree2], [{ foo: "43" }]); tree1.merge(tree2, false); @@ -2244,16 +2199,14 @@ describe("Editing", () => { const tree1 = checkoutWithContent(emptyJsonContent); const tree2 = tree1.branch(); - tree1.editor.optionalField(rootField).set(singleJsonCursor("41"), true); + tree1.editor.optionalField(rootField).set(chunkFromJsonField(["41"]), true); - tree2.editor.optionalField(rootField).set(singleJsonCursor({ foo: "42" }), true); + tree2.editor.optionalField(rootField).set(chunkFromJsonField([{ foo: "42" }]), true); tree1.merge(tree2, false); const editor = tree2.editor.valueField({ parent: rootNode, field: brand("foo") }); - editor.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "43" }), - ); + editor.set(chunkFromJsonField(["43"])); tree1.merge(tree2, false); tree2.rebaseOnto(tree1); @@ -2270,12 +2223,10 @@ describe("Editing", () => { parent: rootNode, field: brand("bar"), }) - .set(singleJsonCursor("456"), false); + .set(chunkFromJsonField(["456"]), false); const editor = tree2.editor.valueField({ parent: rootNode, field: brand("foo") }); - editor.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "42" }), - ); + editor.set(chunkFromJsonField(["42"])); tree1.merge(tree2, false); tree2.rebaseOnto(tree1); @@ -2288,14 +2239,12 @@ describe("Editing", () => { const tree2 = tree1.branch(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree1.events); - tree1.editor.optionalField(rootField).set(singleJsonCursor({ foo: "41" }), false); + tree1.editor.optionalField(rootField).set(chunkFromJsonField([{ foo: "41" }]), false); undoStack.pop()?.revert(); const editor = tree2.editor.valueField({ parent: rootNode, field: brand("foo") }); - editor.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "42" }), - ); + editor.set(chunkFromJsonField(["42"])); tree1.merge(tree2, false); tree2.rebaseOnto(tree1); @@ -2308,11 +2257,11 @@ describe("Editing", () => { const tree1 = checkoutWithContent(emptyJsonContent); const tree2 = tree1.branch(); - tree1.editor.optionalField(rootField).set(singleJsonCursor("1"), true); - tree2.editor.optionalField(rootField).set(singleJsonCursor("2"), true); + tree1.editor.optionalField(rootField).set(chunkFromJsonField(["1"]), true); + tree2.editor.optionalField(rootField).set(chunkFromJsonField(["2"]), true); tree2.rebaseOnto(tree1); - tree1.editor.optionalField(rootField).set(singleJsonCursor("1 again"), false); + tree1.editor.optionalField(rootField).set(chunkFromJsonField(["1 again"]), false); tree2.rebaseOnto(tree1); expectJsonTree(tree2, ["2"]); @@ -2322,7 +2271,7 @@ describe("Editing", () => { const tree1 = makeTreeFromJson("42"); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree1.events); - tree1.editor.optionalField(rootField).set(singleJsonCursor("43"), false); + tree1.editor.optionalField(rootField).set(chunkFromJsonField(["43"]), false); expectJsonTree(tree1, ["43"]); @@ -2338,11 +2287,11 @@ describe("Editing", () => { tree1.editor .optionalField({ parent: rootNode, field: brand("foo") }) - .set(singleJsonCursor("A"), true); + .set(chunkFromJsonField(["A"]), true); tree2.editor .optionalField({ parent: rootNode, field: brand("bar") }) - .set(singleJsonCursor("B"), true); + .set(chunkFromJsonField(["B"]), true); expectJsonTree(tree1, [{ foo: "A" }]); expectJsonTree(tree2, [{ bar: "B" }]); @@ -2359,7 +2308,7 @@ describe("Editing", () => { { title: "replace A with B", delegate: (tree: ITreeCheckout) => - tree.editor.optionalField(rootField).set(singleJsonCursor("B"), false), + tree.editor.optionalField(rootField).set(chunkFromJsonField(["B"]), false), isEmptyAfter: false, }, { @@ -2373,7 +2322,7 @@ describe("Editing", () => { { title: "replaced with C", delegate: (tree: ITreeCheckout, isEmpty: boolean) => - tree.editor.optionalField(rootField).set(singleJsonCursor("C"), isEmpty), + tree.editor.optionalField(rootField).set(chunkFromJsonField(["C"]), isEmpty), }, { title: "cleared", @@ -2452,7 +2401,7 @@ describe("Editing", () => { const tree2 = tree.branch(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree2.events); - tree.editor.optionalField(rootField).set(singleJsonCursor("43"), false); + tree.editor.optionalField(rootField).set(chunkFromJsonField(["43"]), false); // Replace 42 with undefined tree2.editor.optionalField(rootField).set(undefined, false); @@ -2477,13 +2426,13 @@ describe("Editing", () => { const tree2 = tree.branch(); tree2.transaction.start(); - tree2.editor.optionalField(rootField).set(singleJsonCursor("43"), false); - tree2.editor.optionalField(rootField).set(singleJsonCursor("44"), false); + tree2.editor.optionalField(rootField).set(chunkFromJsonField(["43"]), false); + tree2.editor.optionalField(rootField).set(chunkFromJsonField(["44"]), false); tree2.transaction.commit(); - tree2.editor.optionalField(rootField).set(singleJsonCursor("45"), false); + tree2.editor.optionalField(rootField).set(chunkFromJsonField(["45"]), false); - tree.editor.optionalField(rootField).set(singleJsonCursor("46"), false); + tree.editor.optionalField(rootField).set(chunkFromJsonField(["46"]), false); tree2.rebaseOnto(tree); tree.merge(tree2, false); @@ -2495,19 +2444,17 @@ describe("Editing", () => { const tree1 = checkoutWithContent(emptyJsonContent); const tree2 = tree1.branch(); - tree1.editor.optionalField(rootField).set(singleJsonCursor("41"), true); + tree1.editor.optionalField(rootField).set(chunkFromJsonField(["41"]), true); tree2.transaction.start(); - tree2.editor.optionalField(rootField).set(singleJsonCursor({ foo: "42" }), true); + tree2.editor.optionalField(rootField).set(chunkFromJsonField([{ foo: "42" }]), true); expectJsonTree([tree1], ["41"]); expectJsonTree([tree2], [{ foo: "42" }]); tree2.editor .valueField({ parent: rootNode, field: brand("foo") }) - .set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "43" }), - ); + .set(chunkFromJsonField(["43"])); expectJsonTree([tree2], [{ foo: "43" }]); tree2.transaction.commit(); @@ -2523,11 +2470,11 @@ describe("Editing", () => { const tree2 = tree.branch(); tree2.transaction.start(); - tree2.editor.optionalField(rootField).set(singleJsonCursor("42"), true); + tree2.editor.optionalField(rootField).set(chunkFromJsonField(["42"]), true); tree2.editor.optionalField(rootField).set(undefined, false); tree2.transaction.commit(); - tree.editor.optionalField(rootField).set(singleJsonCursor("43"), true); + tree.editor.optionalField(rootField).set(chunkFromJsonField(["43"]), true); tree2.rebaseOnto(tree); tree.merge(tree2, false); @@ -2539,7 +2486,7 @@ describe("Editing", () => { const tree = makeTreeFromJson(1); const fork = tree.branch(); - tree.editor.optionalField(rootField).set(singleJsonCursor(2), false); + tree.editor.optionalField(rootField).set(chunkFromJsonField([2]), false); const { undoStack, redoStack } = createTestUndoRedoStacks(fork.events); fork.editor.optionalField(rootField).set(undefined, false); @@ -2559,15 +2506,12 @@ describe("Editing", () => { const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, singleJsonCursor({})); + rootSequence.insert(0, chunkFromJsonField(["{}"])); const treeSequence = tree.editor.sequenceField({ parent: rootNode, field: brand("foo"), }); - treeSequence.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "bar" }), - ); + treeSequence.insert(0, chunkFromJsonField(["bar"])); const tree2 = tree.branch(); @@ -2585,7 +2529,7 @@ describe("Editing", () => { parentIndex: 0, }); const tree2Sequence = tree2.editor.sequenceField(rootField); - tree2Sequence.insert(1, singleJsonCursor("b")); + tree2Sequence.insert(1, chunkFromJsonField(["b"])); tree2.transaction.commit(); tree.merge(tree2, false); @@ -2599,15 +2543,12 @@ describe("Editing", () => { const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, singleJsonCursor({})); + rootSequence.insert(0, chunkFromJsonField(["{}"])); const treeSequence = tree.editor.sequenceField({ parent: rootNode, field: brand("foo"), }); - treeSequence.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "A" }), - ); + treeSequence.insert(0, chunkFromJsonField(["A"])); const tree2 = tree.branch(); @@ -2615,12 +2556,7 @@ describe("Editing", () => { // Modify the field containing the node existence constraint then remove its ancestor tree.transaction.start(); - tree.editor - .sequenceField(fooPath) - .insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "C" }), - ); + tree.editor.sequenceField(fooPath).insert(0, chunkFromJsonField(["C"])); remove(tree, 0, 1); tree.transaction.commit(); @@ -2635,10 +2571,7 @@ describe("Editing", () => { const tree2Sequence = tree2.editor.sequenceField(rootField); // Insert B if the child of A is still attached - tree2Sequence.insert( - 1, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "B" }), - ); + tree2Sequence.insert(1, chunkFromJsonField(["B"])); tree2.transaction.commit(); tree.merge(tree2, false); @@ -2671,10 +2604,7 @@ describe("Editing", () => { tree2.editor.addNodeExistsConstraint(dPath); const tree2RootSequence = tree2.editor.sequenceField(rootField); // Should not be inserted because D has been concurrently removed - tree2RootSequence.insert( - 1, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "B" }), - ); + tree2RootSequence.insert(1, chunkFromJsonField(["B"])); tree2.transaction.commit(); tree2.rebaseOnto(tree); @@ -2699,15 +2629,12 @@ describe("Editing", () => { it("optional field node exists constraint", () => { const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, singleJsonCursor({})); + rootSequence.insert(0, chunkFromJsonField(["{}"])); const optional = tree.editor.optionalField({ parent: rootNode, field: brand("foo"), }); - optional.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "x" }), - true, - ); + optional.set(chunkFromJsonField(["x"]), true); const tree2 = tree.branch(); @@ -2722,7 +2649,7 @@ describe("Editing", () => { }); tree2.editor .sequenceField({ parent: rootNode, field: brand("bar") }) - .insert(0, singleJsonCursor(1)); + .insert(0, chunkFromJsonField([1])); tree2.transaction.commit(); tree.merge(tree2, false); @@ -2735,16 +2662,13 @@ describe("Editing", () => { const tree = checkoutWithContent(emptyJsonContent); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, singleJsonCursor({})); + rootSequence.insert(0, chunkFromJsonField(["{}"])); const optional = tree.editor.optionalField({ parent: rootNode, field: brand("foo"), }); - optional.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "x" }), - true, - ); + optional.set(chunkFromJsonField(["x"]), true); const tree2 = tree.branch(); @@ -2759,7 +2683,7 @@ describe("Editing", () => { }); tree2.editor .sequenceField({ parent: rootNode, field: brand("bar") }) - .insert(0, singleJsonCursor(1)); + .insert(0, chunkFromJsonField([1])); tree2.transaction.commit(); tree.merge(tree2, false); @@ -2776,29 +2700,20 @@ describe("Editing", () => { // Insert "a" // State should be: ["a"] const sequence = tree.editor.sequenceField(rootField); - sequence.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "a" }), - ); + sequence.insert(0, chunkFromJsonField(["a"])); // Insert "b" after "a" with constraint that "a" exists. // State should be: ["a", "b"] tree.transaction.start(); tree.editor.addNodeExistsConstraint(rootNode); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert( - 1, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "b" }), - ); + rootSequence.insert(1, chunkFromJsonField(["b"])); tree.transaction.commit(); // Make a concurrent edit to rebase over that inserts into root sequence // State should be (to tree2): ["c"] const tree2RootSequence = tree2.editor.sequenceField(rootField); - tree2RootSequence.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "c" }), - ); + tree2RootSequence.insert(0, chunkFromJsonField(["c"])); tree.merge(tree2, false); tree2.rebaseOnto(tree); @@ -2817,10 +2732,7 @@ describe("Editing", () => { parent: rootNode, field: brand("foo"), }); - sequence.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "a" }), - ); + sequence.insert(0, chunkFromJsonField(["a"])); tree.editor.addNodeExistsConstraint({ parent: rootNode, @@ -2828,19 +2740,13 @@ describe("Editing", () => { parentIndex: 0, }); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert( - 1, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "b" }), - ); + rootSequence.insert(1, chunkFromJsonField(["b"])); tree.transaction.commit(); // Insert "c" concurrently so that we rebase over something // State should be (to tree2): [{}, "c"] const tree2Sequence = tree2.editor.sequenceField(rootField); - tree2Sequence.insert( - 1, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "c" }), - ); + tree2Sequence.insert(1, chunkFromJsonField(["c"])); tree.merge(tree2, false); tree2.rebaseOnto(tree); @@ -2851,15 +2757,12 @@ describe("Editing", () => { it("a change can depend on the existence of a node that is built in a prior change whose constraint was violated", () => { const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, singleJsonCursor({})); + rootSequence.insert(0, chunkFromJsonField(["{}"])); const optional = tree.editor.optionalField({ parent: rootNode, field: brand("foo"), }); - optional.set( - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "x" }), - true, - ); + optional.set(chunkFromJsonField(["x"]), true); const tree2 = tree.branch(); @@ -2874,7 +2777,7 @@ describe("Editing", () => { }); tree2.editor .sequenceField({ parent: rootNode, field: brand("bar") }) - .insert(0, singleJsonCursor({ baz: 42 })); + .insert(0, chunkFromJsonField([{ baz: 42 }])); tree2.transaction.commit(); expectJsonTree([tree2], [{ foo: "x", bar: { baz: 42 } }]); // This edit require the node `{ baz: 42 }` to have been built @@ -2887,7 +2790,7 @@ describe("Editing", () => { }, field: brand("baz"), }) - .set(singleJsonCursor(43), false); + .set(chunkFromJsonField([43]), false); tree.merge(tree2, false); tree2.rebaseOnto(tree); @@ -2910,10 +2813,7 @@ describe("Editing", () => { parent: rootNode, field: brand("foo"), }); - sequence.insert( - 0, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "a" }), - ); + sequence.insert(0, chunkFromJsonField(["a"])); tree2.editor.addNodeExistsConstraint({ parent: rootNode, @@ -2921,10 +2821,7 @@ describe("Editing", () => { parentIndex: 0, }); const rootSequence = tree2.editor.sequenceField(rootField); - rootSequence.insert( - 1, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "b" }), - ); + rootSequence.insert(1, chunkFromJsonField(["b"])); tree2.transaction.commit(); tree.merge(tree2, false); @@ -2971,7 +2868,7 @@ describe("Editing", () => { parentIndex: 0, }); const tree2RootSequence = tree2.editor.sequenceField(rootField); - tree2RootSequence.insert(2, singleJsonCursor({})); + tree2RootSequence.insert(2, chunkFromJsonField(["{}"])); tree2.transaction.commit(); tree.merge(tree2, false); @@ -3022,10 +2919,7 @@ describe("Editing", () => { parentIndex: 0, }); const tree2RootSequence = tree2.editor.sequenceField(rootField); - tree2RootSequence.insert( - 2, - cursorForJsonableTreeNode({ type: brand(stringSchema.identifier), value: "b" }), - ); + tree2RootSequence.insert(2, chunkFromJsonField(["b"])); tree2.transaction.commit(); tree.merge(tree2, false); @@ -3046,7 +2940,7 @@ describe("Editing", () => { tree.transaction.start(); tree.editor .valueField({ parent: rootNode, field: brand("foo") }) - .set(singleJsonCursor("B")); + .set(chunkFromJsonField(["B"])); tree.editor.addNodeExistsConstraintOnRevert({ parent: rootNode, parentField: brand("foo"), @@ -3061,7 +2955,7 @@ describe("Editing", () => { // a different node on filed "bar". tree.editor .optionalField({ parent: rootNode, field: brand("bar") }) - .set(singleJsonCursor("C"), true); + .set(chunkFromJsonField(["C"]), true); // This revert should apply since its constraint has not been violated changedFooAtoB.revert(); @@ -3080,7 +2974,7 @@ describe("Editing", () => { tree.transaction.start(); tree.editor .valueField({ parent: rootNode, field: brand("foo") }) - .set(singleJsonCursor("B")); + .set(chunkFromJsonField(["B"])); tree.editor.addNodeExistsConstraintOnRevert({ parent: rootNode, parentField: brand("foo"), @@ -3095,7 +2989,7 @@ describe("Editing", () => { // node "B" to "C" on field "foo". tree.editor .valueField({ parent: rootNode, field: brand("foo") }) - .set(singleJsonCursor("C")); + .set(chunkFromJsonField(["C"])); // This revert should do nothing since its constraint has been violated. changedFooAtoB.revert(); @@ -3114,7 +3008,7 @@ describe("Editing", () => { branch.transaction.start(); branch.editor .valueField({ parent: rootNode, field: brand("bar") }) - .set(singleJsonCursor("new")); + .set(chunkFromJsonField(["new"])); branch.editor.addNodeExistsConstraintOnRevert({ parent: rootNode, parentField: brand("foo"), @@ -3127,7 +3021,7 @@ describe("Editing", () => { // the undo constraint on the branch transaction when the branch is rebased into tree. tree.editor .valueField({ parent: rootNode, field: brand("foo") }) - .set(singleJsonCursor("C")); + .set(chunkFromJsonField(["C"])); branch.rebaseOnto(tree); const stack = createTestUndoRedoStacks(tree.events); @@ -3227,10 +3121,10 @@ describe("Editing", () => { branch.editor .sequenceField({ parent: rootNode, field: brand("seq") }) - .insert(0, singleJsonCursor(1)); + .insert(0, chunkFromJsonField([1])); branch.editor .optionalField({ parent: rootNode, field: brand("opt") }) - .set(singleJsonCursor(2), true); + .set(chunkFromJsonField([2]), true); let cursor = branch.forest.allocateCursor(); branch.forest.moveCursorToPath( @@ -3247,7 +3141,7 @@ describe("Editing", () => { tree.editor .sequenceField({ parent: rootNode, field: brand("foo") }) - .insert(0, singleJsonCursor(3)); + .insert(0, chunkFromJsonField([3])); tree.merge(branch, false); branch.rebaseOnto(tree); @@ -3296,19 +3190,16 @@ describe("Editing", () => { getInnerSequenceFieldPath({ parent: root1Path, field: brand("foo") }), ); foo0.remove(1, 1); - foo0.insert(1, cursorForJsonableTreeNode({ type: brand("Number"), value: 41 })); + foo1.insert(1, chunkFromJsonableField([{ type: brand("Number"), value: 41 }])); foo0.remove(2, 1); - foo0.insert(2, cursorForJsonableTreeNode({ type: brand("Number"), value: 42 })); + foo1.insert(1, chunkFromJsonableField([{ type: brand("Number"), value: 42 }])); foo0.remove(0, 1); - rootSequence.insert(0, cursorForJsonableTreeNode({ type: brand("Test") })); + rootSequence.insert(0, chunkFromJsonableField([{ type: brand("Test") }])); foo1.remove(0, 1); - foo1.insert( - 0, - cursorForJsonableTreeNode({ type: brand("Number"), value: "RootValue2" }), - ); - foo1.insert(0, cursorForJsonableTreeNode({ type: brand("Test") })); + foo1.insert(0, chunkFromJsonableField([{ type: brand("Number"), value: "RootValue2" }])); + foo1.insert(0, chunkFromJsonableField([{ type: brand("Test") }])); foo1.remove(1, 1); - foo1.insert(1, cursorForJsonableTreeNode({ type: brand("Number"), value: 82 })); + foo1.insert(1, chunkFromJsonableField([{ type: brand("Number"), value: 82 }])); // Aborting the transaction should restore the forest branch.transaction.abort(); @@ -3336,12 +3227,12 @@ describe("Editing", () => { tree.transaction.start(); tree.editor .optionalField({ parent: rootNode, field: brand("foo") }) - .set(singleJsonCursor("A"), true); + .set(chunkFromJsonField(["A"]), true); moveWithin(tree.editor, rootField, 0, 1, 0); - tree.editor.sequenceField(rootField).insert(0, singleJsonCursor({})); + tree.editor.sequenceField(rootField).insert(0, chunkFromJsonField(["{}"])); tree.editor .optionalField({ parent: rootNode, field: brand("bar") }) - .set(singleJsonCursor("B"), true); + .set(chunkFromJsonField(["B"]), true); tree.transaction.commit(); tree.transaction.abort(); diff --git a/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts b/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts index 40f01a0a4900..7d193edff5f6 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTree.bench.ts @@ -14,7 +14,6 @@ import { import { FlushMode } from "@fluidframework/runtime-definitions/internal"; import { EmptyKey, rootFieldKey } from "../../core/index.js"; -import { singleJsonCursor } from "../json/index.js"; // eslint-disable-next-line import/no-internal-modules import { typeboxValidator } from "../../external-utilities/typeboxValidator.js"; import { @@ -46,6 +45,7 @@ import { StringArray, TestTreeProviderLite, checkoutWithContent, + chunkFromJsonField, flexTreeViewWithContent, toJsonableTree, } from "../utils.js"; @@ -251,7 +251,7 @@ describe("SharedTree benchmarks", () => { for (let value = 1; value <= setCount; value++) { tree.editor .valueField({ parent: path, field: localFieldKey }) - .set(singleJsonCursor(value)); + .set(chunkFromJsonField([value])); } const after = state.timer.now(); duration = state.timer.toSeconds(before, after); @@ -301,7 +301,7 @@ describe("SharedTree benchmarks", () => { const before = state.timer.now(); for (let value = 1; value <= setCount; value++) { editor.remove(nodeIndex, 1); - editor.insert(nodeIndex, singleJsonCursor(value)); + editor.insert(nodeIndex, chunkFromJsonField([value])); } const after = state.timer.now(); duration = state.timer.toSeconds(before, after); diff --git a/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts b/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts index 8f94aa3b71a8..99e5cc5137a1 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTree.spec.ts @@ -39,7 +39,6 @@ import { MockNodeKeyManager, TreeCompressionStrategy, TreeStatus, - cursorForJsonableTreeNode, } from "../../feature-libraries/index.js"; import { ObjectForest, @@ -87,6 +86,7 @@ import { StringArray, NumberArray, validateViewConsistency, + chunkFromJsonableField, } from "../utils.js"; import { configuredSharedTree, TreeFactory } from "../../treeFactory.js"; import type { ISharedObjectKind } from "@fluidframework/shared-object-base/internal"; @@ -235,10 +235,12 @@ describe("SharedTree", () => { field: rootFieldKey, }); field.set( - cursorForJsonableTreeNode({ - type: brand(handleSchema.identifier), - value: provider.trees[0].handle, - }), + chunkFromJsonableField([ + { + type: brand(handleSchema.identifier), + value: provider.trees[0].handle, + }, + ]), true, ); }); diff --git a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeFamily.spec.ts b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeFamily.spec.ts index f429928b00ba..ee93b5633ceb 100644 --- a/packages/dds/tree/src/test/shared-tree/sharedTreeChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/sharedTreeChangeFamily.spec.ts @@ -35,8 +35,12 @@ import type { // eslint-disable-next-line import/no-internal-modules } from "../../shared-tree/sharedTreeChangeTypes.js"; import { ajvValidator } from "../codec/index.js"; -import { failCodecFamily, mintRevisionTag, testRevisionTagCodec } from "../utils.js"; -import { singleJsonCursor } from "../json/index.js"; +import { + chunkFromJsonField, + failCodecFamily, + mintRevisionTag, + testRevisionTagCodec, +} from "../utils.js"; const dataChanges: ModularChangeset[] = []; const codecOptions: ICodecOptions = { jsonValidator: ajvValidator }; @@ -53,10 +57,10 @@ const defaultEditor = new DefaultEditBuilder(modularFamily, mintRevisionTag, (ta // Side effects results in `dataChanges` being populated defaultEditor .valueField({ parent: undefined, field: rootFieldKey }) - .set(singleJsonCursor("X")); + .set(chunkFromJsonField(["X"])); defaultEditor .valueField({ parent: undefined, field: rootFieldKey }) - .set(singleJsonCursor("Y")); + .set(chunkFromJsonField(["Y"])); const dataChange1 = dataChanges[0]; const dataChange2 = dataChanges[1]; diff --git a/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts b/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts index 04a309440cbb..c4249d10fe4a 100644 --- a/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts @@ -20,7 +20,7 @@ import { EmptyKey, type RevertibleFactory, } from "../../core/index.js"; -import { FieldKinds, cursorForJsonableTreeField } from "../../feature-libraries/index.js"; +import { FieldKinds } from "../../feature-libraries/index.js"; import { getBranch, Tree, @@ -31,6 +31,7 @@ import { } from "../../shared-tree/index.js"; import { TestTreeProviderLite, + chunkFromJsonableField, createTestUndoRedoStacks, expectSchemaEqual, getView, @@ -171,7 +172,7 @@ describe("sharedTreeView", () => { checkout.editor .optionalField(rootField) .set( - cursorForJsonableTreeField([{ type: brand(stringSchema.identifier), value: "A" }]), + chunkFromJsonableField([{ type: brand(stringSchema.identifier), value: "A" }]), true, ); @@ -198,7 +199,7 @@ describe("sharedTreeView", () => { checkout.editor .optionalField(rootField) .set( - cursorForJsonableTreeField([{ type: brand(stringSchema.identifier), value: "A" }]), + chunkFromJsonableField([{ type: brand(stringSchema.identifier), value: "A" }]), true, ); checkout.updateSchema(toStoredSchema(OptionalString)); diff --git a/packages/dds/tree/src/test/shared-tree/undo.spec.ts b/packages/dds/tree/src/test/shared-tree/undo.spec.ts index b3709482e503..12904f4b0828 100644 --- a/packages/dds/tree/src/test/shared-tree/undo.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/undo.spec.ts @@ -14,6 +14,7 @@ import { singleJsonCursor } from "../json/index.js"; import type { ITreeCheckout } from "../../shared-tree/index.js"; import { type JsonCompatible, brand } from "../../util/index.js"; import { + chunkFromJsonField, createTestUndoRedoStacks, expectJsonTree, moveWithin, @@ -362,9 +363,9 @@ describe("Undo and redo", () => { const tree2 = tree1.branch(); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree2.events); - tree1.editor.sequenceField(rootField).insert(3, singleJsonCursor(1)); - tree2.editor.sequenceField(rootField).insert(0, singleJsonCursor(2)); - tree2.editor.sequenceField(rootField).insert(0, singleJsonCursor(3)); + tree1.editor.sequenceField(rootField).insert(3, chunkFromJsonField([1])); + tree2.editor.sequenceField(rootField).insert(0, chunkFromJsonField([2])); + tree2.editor.sequenceField(rootField).insert(0, chunkFromJsonField([3])); undoStack.pop()?.revert(); expectJsonTree(tree2, [2, 0, 0, 0]); tree2.rebaseOnto(tree1); @@ -404,8 +405,8 @@ describe("Undo and redo", () => { const { undoStack: undoStack1, unsubscribe: unsubscribe1 } = createTestUndoRedoStacks( tree1.events, ); - tree1.editor.sequenceField(rootField).insert(0, singleJsonCursor("A")); - tree1.editor.sequenceField(rootField).insert(2, singleJsonCursor("C")); + tree1.editor.sequenceField(rootField).insert(0, chunkFromJsonField(["A"])); + tree1.editor.sequenceField(rootField).insert(2, chunkFromJsonField(["C"])); undoStack1.pop()?.revert(); undoStack1.pop()?.revert(); @@ -427,7 +428,7 @@ describe("Undo and redo", () => { const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); tree.transaction.start(); - tree.editor.sequenceField(rootField).insert(2, singleJsonCursor("C")); + tree.editor.sequenceField(rootField).insert(2, chunkFromJsonField(["C"])); tree.editor.sequenceField(rootField).remove(0, 1); tree.transaction.commit(); @@ -444,7 +445,7 @@ describe("Undo and redo", () => { const { undoStack, redoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); const branch = tree.branch(); - branch.editor.sequenceField(rootField).insert(2, singleJsonCursor("C")); + branch.editor.sequenceField(rootField).insert(2, chunkFromJsonField(["C"])); branch.editor.sequenceField(rootField).remove(0, 1); tree.merge(branch); @@ -467,13 +468,13 @@ describe("Undo and redo", () => { const branch = tree.branch(); - branch.editor.sequenceField(rootField).insert(2, singleJsonCursor("C")); + branch.editor.sequenceField(rootField).insert(2, chunkFromJsonField(["C"])); tree.merge(branch, false); expectJsonTree(tree, ["A", "B", "C"]); undoStack.pop()?.revert(); expectJsonTree(tree, ["A", "B"]); - branch.editor.sequenceField(rootField).insert(2, singleJsonCursor("C")); + branch.editor.sequenceField(rootField).insert(2, chunkFromJsonField(["C"])); tree.merge(branch); expectJsonTree(tree, ["A", "B", "C"]); undoStack.pop()?.revert(); diff --git a/packages/dds/tree/src/test/simple-tree/api/treeNodeApi.spec.ts b/packages/dds/tree/src/test/simple-tree/api/treeNodeApi.spec.ts index 6edcbae2c1b1..3f3402dcbdc8 100644 --- a/packages/dds/tree/src/test/simple-tree/api/treeNodeApi.spec.ts +++ b/packages/dds/tree/src/test/simple-tree/api/treeNodeApi.spec.ts @@ -11,7 +11,6 @@ import { import { type UpPath, rootFieldKey } from "../../../core/index.js"; import { - cursorForJsonableTreeNode, MockNodeKeyManager, TreeStatus, type StableNodeKey, @@ -29,7 +28,7 @@ import { TreeViewConfiguration, type UnsafeUnknownSchema, } from "../../../simple-tree/index.js"; -import { getView, validateUsageError } from "../../utils.js"; +import { chunkFromJsonableField, getView, validateUsageError } from "../../utils.js"; import { getViewForForkedBranch, hydrate } from "../utils.js"; import { brand, type areSafelyAssignable, type requireTrue } from "../../../util/index.js"; @@ -778,10 +777,10 @@ describe("treeNodeApi", () => { const branch = checkout.branch(); branch.editor .valueField({ parent: rootNode, field: brand("prop1") }) - .set(cursorForJsonableTreeNode({ type: brand(numberSchema.identifier), value: 2 })); + .set(chunkFromJsonableField([{ type: brand(numberSchema.identifier), value: 2 }])); branch.editor .valueField({ parent: rootNode, field: brand("prop2") }) - .set(cursorForJsonableTreeNode({ type: brand(numberSchema.identifier), value: 2 })); + .set(chunkFromJsonableField([{ type: brand(numberSchema.identifier), value: 2 }])); checkout.merge(branch); diff --git a/packages/dds/tree/src/test/utils.ts b/packages/dds/tree/src/test/utils.ts index dd7f4d1f6891..7deb0d5689f7 100644 --- a/packages/dds/tree/src/test/utils.ts +++ b/packages/dds/tree/src/test/utils.ts @@ -112,7 +112,6 @@ import { cursorForJsonableTreeField, initializeForest, chunkFieldSingle, - emptyChunk, } from "../feature-libraries/index.js"; // eslint-disable-next-line import/no-internal-modules import { makeSchemaCodec } from "../feature-libraries/schema-index/codec.js"; @@ -160,7 +159,7 @@ import { } from "../util/index.js"; import { isFluidHandle, toFluidHandleInternal } from "@fluidframework/runtime-utils/internal"; import type { Client } from "@fluid-private/test-dds-utils"; -import { cursorToJsonObject, fieldJsonCursor } from "./json/index.js"; +import { cursorToJsonObject, fieldJsonCursor, singleJsonCursor } from "./json/index.js"; // eslint-disable-next-line import/no-internal-modules import type { TreeSimpleContent } from "./feature-libraries/flex-tree/utils.js"; import type { Transactor } from "../shared-tree-core/index.js"; @@ -623,7 +622,7 @@ export function validateFuzzTreeConsistency( function contentToJsonableTree( content: TreeSimpleContent | TreeStoredContent, ): JsonableTree[] { - return jsonableTreeFromFieldCursor((content.initialTree ?? emptyChunk).cursor()); + return jsonableTreeFromFieldCursor(normalizeNewFieldContent(content.initialTree)); } export function validateTreeContent(tree: ITreeCheckout, content: TreeSimpleContent): void { @@ -795,7 +794,7 @@ export function flexTreeViewWithContent( export function forestWithContent(content: TreeStoredContent): IEditableForest { const forest = buildForest(); - const fieldCursor = (content.initialTree ?? emptyChunk).cursor(); + const fieldCursor = normalizeNewFieldContent(content.initialTree); initializeForest(forest, fieldCursor, testRevisionTagCodec, testIdCompressor); return forest; } @@ -815,7 +814,7 @@ export const IdentifierSchema = sf.object("identifier-object", { export function makeTreeFromJson(json: JsonCompatible): ITreeCheckout { return checkoutWithContent({ schema: toStoredSchema(JsonAsTree.Tree), - initialTree: chunkFromJsonField([json]), + initialTree: singleJsonCursor(json), }); } From 333e75fddc6f283d4d84b06193e0e1d609420491 Mon Sep 17 00:00:00 2001 From: Yann Achard Date: Thu, 6 Mar 2025 14:26:39 -0800 Subject: [PATCH 5/7] all tests pass --- packages/dds/tree/src/core/tree/visitDelta.ts | 2 +- .../chunked-forest/chunkTree.ts | 3 +++ .../default-schema/defaultEditBuilder.ts | 3 ++- .../feature-libraries/flex-tree/lazyField.ts | 6 ++--- .../modular-schema/modularChangeFamily.ts | 5 ++++ .../tree/src/test/shared-tree/editing.spec.ts | 24 +++++++++---------- 6 files changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/dds/tree/src/core/tree/visitDelta.ts b/packages/dds/tree/src/core/tree/visitDelta.ts index 65ac76d6c7de..3b88bbd1c651 100644 --- a/packages/dds/tree/src/core/tree/visitDelta.ts +++ b/packages/dds/tree/src/core/tree/visitDelta.ts @@ -55,7 +55,7 @@ import type { RevisionTag, TreeChunk } from "../index.js"; * This needs to happen last to allow modifications to detached roots to be applied before they are destroyed. * * The details of the delta visit algorithm can impact how/when events are emitted by the objects that own the visitors. - * For example, as of 2024-03-27, the subtreecChanged event of an AnchorNode is emitted when exiting a node during a + * For example, as of 2024-03-27, the subtreeChanged event of an AnchorNode is emitted when exiting a node during a * delta visit, and thus the two-pass nature of the algorithm means the event fires twice for any given change. * This two-pass nature also means that the event may fire at a time where no change is visible in the tree. E.g., * if a node is being replaced, when the event fires during the detach pass no change in the tree has happened so the diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts index 94aadfe09ed3..b90b2ef315a9 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts @@ -174,6 +174,9 @@ export function chunkField( policy: ChunkCompressor, ): TreeChunk[] { const length = cursor.getFieldLength(); + if (length === 0) { + return []; + } const started = cursor.firstNode(); assert(started, 0x57c /* field to chunk should have at least one node */); return chunkRange(cursor, policy, length, false); diff --git a/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts b/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts index f9b2dc549363..57dac44362eb 100644 --- a/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts +++ b/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts @@ -373,7 +373,8 @@ export class DefaultEditBuilder implements ChangeFamilyEditor, IDefaultEditBuild public sequenceField(field: FieldUpPath): SequenceFieldEditBuilder { return { insert: (index: number, content: TreeChunk): void => { - if (content.topLevelLength === 0) { + const length = content.topLevelLength; + if (length === 0) { return; } diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts b/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts index c8fcc139b0ba..ee003ea79fa0 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts @@ -56,7 +56,7 @@ import { } from "./lazyEntity.js"; import { type LazyTreeNode, makeTree } from "./lazyNode.js"; import { indexForAt, treeStatusFromAnchorCache } from "./utilities.js"; -import { cursorForMapTreeField, cursorForMapTreeNode } from "../mapTreeCursor.js"; +import { cursorForMapTreeField } from "../mapTreeCursor.js"; /** * Reuse fields. @@ -302,7 +302,7 @@ export class ReadonlyLazyValueField extends LazyField implements FlexTreeRequire export class LazyValueField extends ReadonlyLazyValueField implements FlexTreeRequiredField { public override editor: ValueFieldEditBuilder = { set: (newContent) => { - this.valueFieldEditor().set(cursorForMapTreeNode(newContent)); + this.valueFieldEditor().set(cursorForMapTreeField([newContent])); }, }; @@ -321,7 +321,7 @@ export class LazyOptionalField extends LazyField implements FlexTreeOptionalFiel public editor: OptionalFieldEditBuilder = { set: (newContent, wasEmpty) => { this.optionalEditor().set( - newContent !== undefined ? cursorForMapTreeNode(newContent) : newContent, + newContent !== undefined ? cursorForMapTreeField([newContent]) : newContent, wasEmpty, ); }, diff --git a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts index 3bc5269daffc..e0bf83129b06 100644 --- a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts +++ b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts @@ -2655,6 +2655,7 @@ export class ModularEditBuilder extends EditBuilder { * @param content - The node(s) to build. * @param revision - The revision to use for the build. * @returns A description of the edit that can be passed to `submitChanges`. + * The returned object may contain an owning reference to the given TreeChunk. */ public buildTrees( firstId: ChangesetLocalId, @@ -2664,6 +2665,10 @@ export class ModularEditBuilder extends EditBuilder { if (content.topLevelLength === 0) { return { type: "global", revision }; } + + // This content will be added to a GlobalEditDescription whose lifetime exceeds the scope of this function. + content.referenceAdded(); + const builds: ChangeAtomIdBTree = newTupleBTree(); builds.set([revision, firstId], content); diff --git a/packages/dds/tree/src/test/shared-tree/editing.spec.ts b/packages/dds/tree/src/test/shared-tree/editing.spec.ts index 596a2e184f5a..5a4d77988202 100644 --- a/packages/dds/tree/src/test/shared-tree/editing.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/editing.spec.ts @@ -514,10 +514,10 @@ describe("Editing", () => { insert(tree1, 0, "a"); expectJsonTree(tree1, ["a"]); - tree2.editor.sequenceField(rootField).insert(0, chunkFromJsonField(["{}"])); + tree2.editor.sequenceField(rootField).insert(0, chunkFromJsonField([{}])); tree2.editor .sequenceField({ parent: rootNode, field: brand("foo") }) - .insert(0, chunkFromJsonField(["{}"])); + .insert(0, chunkFromJsonField([{}])); expectJsonTree(tree2, [{ foo: {} }]); tree2.rebaseOnto(tree1); @@ -1566,7 +1566,7 @@ describe("Editing", () => { parent: rootNode, field: brand("src"), }); - field.set(chunkFromJsonField(["{}"])); + field.set(chunkFromJsonField([{}])); tree.transaction.commit(); const expectedState: JsonCompatible = [{ src: {}, dst: ["A", "B"] }]; @@ -2506,7 +2506,7 @@ describe("Editing", () => { const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, chunkFromJsonField(["{}"])); + rootSequence.insert(0, chunkFromJsonField([{}])); const treeSequence = tree.editor.sequenceField({ parent: rootNode, field: brand("foo"), @@ -2543,7 +2543,7 @@ describe("Editing", () => { const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, chunkFromJsonField(["{}"])); + rootSequence.insert(0, chunkFromJsonField([{}])); const treeSequence = tree.editor.sequenceField({ parent: rootNode, field: brand("foo"), @@ -2629,7 +2629,7 @@ describe("Editing", () => { it("optional field node exists constraint", () => { const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, chunkFromJsonField(["{}"])); + rootSequence.insert(0, chunkFromJsonField([{}])); const optional = tree.editor.optionalField({ parent: rootNode, field: brand("foo"), @@ -2662,7 +2662,7 @@ describe("Editing", () => { const tree = checkoutWithContent(emptyJsonContent); const { undoStack, unsubscribe } = createTestUndoRedoStacks(tree.events); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, chunkFromJsonField(["{}"])); + rootSequence.insert(0, chunkFromJsonField([{}])); const optional = tree.editor.optionalField({ parent: rootNode, @@ -2757,7 +2757,7 @@ describe("Editing", () => { it("a change can depend on the existence of a node that is built in a prior change whose constraint was violated", () => { const tree = checkoutWithContent(emptyJsonContent); const rootSequence = tree.editor.sequenceField(rootField); - rootSequence.insert(0, chunkFromJsonField(["{}"])); + rootSequence.insert(0, chunkFromJsonField([{}])); const optional = tree.editor.optionalField({ parent: rootNode, field: brand("foo"), @@ -2868,7 +2868,7 @@ describe("Editing", () => { parentIndex: 0, }); const tree2RootSequence = tree2.editor.sequenceField(rootField); - tree2RootSequence.insert(2, chunkFromJsonField(["{}"])); + tree2RootSequence.insert(2, chunkFromJsonField([{}])); tree2.transaction.commit(); tree.merge(tree2, false); @@ -3190,9 +3190,9 @@ describe("Editing", () => { getInnerSequenceFieldPath({ parent: root1Path, field: brand("foo") }), ); foo0.remove(1, 1); - foo1.insert(1, chunkFromJsonableField([{ type: brand("Number"), value: 41 }])); + foo0.insert(1, chunkFromJsonableField([{ type: brand("Number"), value: 41 }])); foo0.remove(2, 1); - foo1.insert(1, chunkFromJsonableField([{ type: brand("Number"), value: 42 }])); + foo0.insert(1, chunkFromJsonableField([{ type: brand("Number"), value: 42 }])); foo0.remove(0, 1); rootSequence.insert(0, chunkFromJsonableField([{ type: brand("Test") }])); foo1.remove(0, 1); @@ -3229,7 +3229,7 @@ describe("Editing", () => { .optionalField({ parent: rootNode, field: brand("foo") }) .set(chunkFromJsonField(["A"]), true); moveWithin(tree.editor, rootField, 0, 1, 0); - tree.editor.sequenceField(rootField).insert(0, chunkFromJsonField(["{}"])); + tree.editor.sequenceField(rootField).insert(0, chunkFromJsonField([{}])); tree.editor .optionalField({ parent: rootNode, field: brand("bar") }) .set(chunkFromJsonField(["B"]), true); From 05bb646ed1a61649879b0a585609ac30c5da89d6 Mon Sep 17 00:00:00 2001 From: Yann Achard Date: Thu, 6 Mar 2025 15:37:07 -0800 Subject: [PATCH 6/7] cleanup --- packages/dds/tree/src/core/tree/delta.ts | 2 +- packages/dds/tree/src/core/tree/visitDelta.ts | 2 +- .../dds/tree/src/core/tree/visitorUtils.ts | 6 +-- .../chunked-forest/chunkedForest.ts | 2 +- .../feature-libraries/chunked-forest/index.ts | 1 - .../default-schema/defaultEditBuilder.ts | 2 - .../default-schema/mappedEditBuilder.ts | 3 ++ .../dds/tree/src/feature-libraries/index.ts | 1 - .../indexing/anchorTreeIndex.ts | 2 +- .../src/feature-libraries/initializeForest.ts | 6 +-- .../modular-schema/modularChangeFamily.ts | 1 + .../object-forest/objectForest.ts | 2 +- .../src/shared-tree/sharedTreeChangeFamily.ts | 1 - .../src/shared-tree/sharedTreeEditBuilder.ts | 16 +++---- .../defaultChangeFamily.spec.ts | 4 +- .../dds/tree/src/test/tree/visitDelta.spec.ts | 44 ++++++++++--------- 16 files changed, 44 insertions(+), 51 deletions(-) diff --git a/packages/dds/tree/src/core/tree/delta.ts b/packages/dds/tree/src/core/tree/delta.ts index 9bce13f29533..9b0217a39ed6 100644 --- a/packages/dds/tree/src/core/tree/delta.ts +++ b/packages/dds/tree/src/core/tree/delta.ts @@ -116,7 +116,7 @@ export interface Root { } /** - * The default representation a chunk (sub-sequence) of inserted content. + * The default representation for a chunk (sub-sequence) of inserted content. */ export type ProtoNodes = TreeChunk; diff --git a/packages/dds/tree/src/core/tree/visitDelta.ts b/packages/dds/tree/src/core/tree/visitDelta.ts index 3b88bbd1c651..e29fa12ac1b8 100644 --- a/packages/dds/tree/src/core/tree/visitDelta.ts +++ b/packages/dds/tree/src/core/tree/visitDelta.ts @@ -263,7 +263,7 @@ export interface DeltaVisitor { * @param destination - The key for a new detached field. * A field with this key must not already exist. */ - create(content: ITreeCursorSynchronous[], destination: FieldKey): void; + create(content: readonly ITreeCursorSynchronous[], destination: FieldKey): void; /** * Recursively destroys the given detached field and all of the nodes within it. * @param detachedField - The key for the detached field to destroy. diff --git a/packages/dds/tree/src/core/tree/visitorUtils.ts b/packages/dds/tree/src/core/tree/visitorUtils.ts index 5c1757bbbc30..fef67a20c118 100644 --- a/packages/dds/tree/src/core/tree/visitorUtils.ts +++ b/packages/dds/tree/src/core/tree/visitorUtils.ts @@ -121,7 +121,7 @@ export interface AnnouncedVisitor extends DeltaVisitor { /** * A hook that is called after all nodes have been created. */ - afterCreate(content: ITreeCursorSynchronous[], destination: FieldKey): void; + afterCreate(content: readonly ITreeCursorSynchronous[], destination: FieldKey): void; beforeDestroy(field: FieldKey, count: number): void; beforeAttach(source: FieldKey, count: number, destination: PlaceIndex): void; afterAttach(source: FieldKey, destination: Range): void; @@ -141,8 +141,8 @@ export interface AnnouncedVisitor extends DeltaVisitor { */ export function createAnnouncedVisitor(visitorFunctions: { free?: () => void; - create?: (content: ITreeCursorSynchronous[], destination: FieldKey) => void; - afterCreate?: (content: ITreeCursorSynchronous[], destination: FieldKey) => void; + create?: (content: readonly ITreeCursorSynchronous[], destination: FieldKey) => void; + afterCreate?: (content: readonly ITreeCursorSynchronous[], destination: FieldKey) => void; beforeDestroy?: (field: FieldKey, count: number) => void; destroy?: (detachedField: FieldKey, count: number) => void; beforeAttach?: (source: FieldKey, count: number, destination: PlaceIndex) => void; diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts index ad20bed2918b..c6a6c5dbd36f 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts @@ -139,7 +139,7 @@ export class ChunkedForest implements IEditableForest { this.forest.#events.emit("beforeChange"); this.forest.roots.fields.delete(detachedField); }, - create(content: ITreeCursorSynchronous[], destination: FieldKey): void { + create(content: readonly ITreeCursorSynchronous[], destination: FieldKey): void { this.forest.#events.emit("beforeChange"); const chunks: TreeChunk[] = content.map((c) => chunkTree(c, { diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/index.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/index.ts index 0c9667191b4a..65146004d157 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/index.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/index.ts @@ -14,7 +14,6 @@ export { chunkField, } from "./chunkTree.js"; export { buildChunkedForest } from "./chunkedForest.js"; -export { emptyChunk } from "./emptyChunk.js"; export { EncodedFieldBatch, type FieldBatch, diff --git a/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts b/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts index 57dac44362eb..902c5703e90b 100644 --- a/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts +++ b/packages/dds/tree/src/feature-libraries/default-schema/defaultEditBuilder.ts @@ -42,7 +42,6 @@ import { sequence, required as valueFieldKind, } from "./defaultFieldKinds.js"; -import type { IIdCompressor } from "@fluidframework/id-compressor"; import type { CellId } from "../sequence-field/index.js"; export type DefaultChangeset = ModularChangeset; @@ -183,7 +182,6 @@ export class DefaultEditBuilder implements ChangeFamilyEditor, IDefaultEditBuild family: ChangeFamily, private readonly mintRevisionTag: () => RevisionTag, changeReceiver: (change: TaggedChange) => void, - private readonly idCompressor?: IIdCompressor, ) { this.modularBuilder = new ModularEditBuilder(family, fieldKinds, changeReceiver); } diff --git a/packages/dds/tree/src/feature-libraries/default-schema/mappedEditBuilder.ts b/packages/dds/tree/src/feature-libraries/default-schema/mappedEditBuilder.ts index 78ec532e8e4f..c93621e248c5 100644 --- a/packages/dds/tree/src/feature-libraries/default-schema/mappedEditBuilder.ts +++ b/packages/dds/tree/src/feature-libraries/default-schema/mappedEditBuilder.ts @@ -11,6 +11,9 @@ import type { ValueFieldEditBuilder, } from "./defaultEditBuilder.js"; +/** + * An IDefaultEditBuilder implementation based on another IDefaultEditBuilder that uses a different content type for insertions. + */ export class MappedEditBuilder implements IDefaultEditBuilder { public constructor( private readonly baseBuilder: IDefaultEditBuilder, diff --git a/packages/dds/tree/src/feature-libraries/index.ts b/packages/dds/tree/src/feature-libraries/index.ts index c5635cfd1fa9..f3e2e88fa40a 100644 --- a/packages/dds/tree/src/feature-libraries/index.ts +++ b/packages/dds/tree/src/feature-libraries/index.ts @@ -99,7 +99,6 @@ export { chunkFieldSingle, buildChunkedForest, defaultChunkPolicy, - emptyChunk, type FieldBatch, type FieldBatchCodec, makeTreeChunker, diff --git a/packages/dds/tree/src/feature-libraries/indexing/anchorTreeIndex.ts b/packages/dds/tree/src/feature-libraries/indexing/anchorTreeIndex.ts index 9297bbf1714b..fa5d16358afd 100644 --- a/packages/dds/tree/src/feature-libraries/indexing/anchorTreeIndex.ts +++ b/packages/dds/tree/src/feature-libraries/indexing/anchorTreeIndex.ts @@ -123,7 +123,7 @@ export class AnchorTreeIndex return createAnnouncedVisitor({ // nodes (and their entire subtrees) are added to the index as soon as they are created - afterCreate: (content: ITreeCursorSynchronous[], destination: FieldKey) => { + afterCreate: (content: readonly ITreeCursorSynchronous[], destination: FieldKey) => { const detachedCursor = this.forest.allocateCursor(); assert( this.forest.tryMoveCursorToField( diff --git a/packages/dds/tree/src/feature-libraries/initializeForest.ts b/packages/dds/tree/src/feature-libraries/initializeForest.ts index 95ebbbc23028..65f0e1b0ea8f 100644 --- a/packages/dds/tree/src/feature-libraries/initializeForest.ts +++ b/packages/dds/tree/src/feature-libraries/initializeForest.ts @@ -13,7 +13,6 @@ import { type RevisionTagCodec, combineVisitors, deltaForRootInitialization, - emptyDelta, makeDetachedFieldIndex, visitDelta, } from "../core/index.js"; @@ -36,10 +35,7 @@ export function initializeForest( visitAnchors = false, ): void { assert(forest.isEmpty, 0x747 /* forest must be empty */); - const delta: DeltaRoot = - content.getFieldLength() === 0 - ? emptyDelta - : deltaForRootInitialization(forest.chunkField(content)); + const delta: DeltaRoot = deltaForRootInitialization(forest.chunkField(content)); let visitor = forest.acquireVisitor(); if (visitAnchors) { assert(forest.anchors.isEmpty(), 0x9b7 /* anchor set must be empty */); diff --git a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts index e0bf83129b06..e86f95efcbb3 100644 --- a/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts +++ b/packages/dds/tree/src/feature-libraries/modular-schema/modularChangeFamily.ts @@ -2034,6 +2034,7 @@ function copyDetachedNodes( const copiedDetachedNodes: DeltaDetachedNodeBuild[] = []; for (const [[major, minor], chunk] of detachedNodes.entries()) { if (chunk.topLevelLength > 0) { + chunk.referenceAdded(); copiedDetachedNodes.push({ id: makeDetachedNodeId(major, minor), trees: chunk, diff --git a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts index 60223bd2a09d..ce3d8a8963b7 100644 --- a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts +++ b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts @@ -168,7 +168,7 @@ export class ObjectForest implements IEditableForest { preEdit(); this.forest.delete(detachedField); } - public create(content: ITreeCursorSynchronous[], destination: FieldKey): void { + public create(content: readonly ITreeCursorSynchronous[], destination: FieldKey): void { preEdit(); this.forest.add(content, destination); this.forest.#events.emit("afterRootFieldCreated", destination); diff --git a/packages/dds/tree/src/shared-tree/sharedTreeChangeFamily.ts b/packages/dds/tree/src/shared-tree/sharedTreeChangeFamily.ts index 6c9b342b04b5..60b7d6823818 100644 --- a/packages/dds/tree/src/shared-tree/sharedTreeChangeFamily.ts +++ b/packages/dds/tree/src/shared-tree/sharedTreeChangeFamily.ts @@ -87,7 +87,6 @@ export class SharedTreeChangeFamily this.modularChangeFamily, mintRevisionTag, changeReceiver, - this.idCompressor, ); } diff --git a/packages/dds/tree/src/shared-tree/sharedTreeEditBuilder.ts b/packages/dds/tree/src/shared-tree/sharedTreeEditBuilder.ts index 5e785bcdab34..530f03b162c7 100644 --- a/packages/dds/tree/src/shared-tree/sharedTreeEditBuilder.ts +++ b/packages/dds/tree/src/shared-tree/sharedTreeEditBuilder.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -import type { IIdCompressor } from "@fluidframework/id-compressor"; import type { ChangeFamilyEditor, RevisionTag, @@ -55,17 +54,12 @@ export class SharedTreeEditBuilder modularChangeFamily: ModularChangeFamily, mintRevisionTag: () => RevisionTag, private readonly changeReceiver: (change: TaggedChange) => void, - idCompressor?: IIdCompressor, ) { - super( - modularChangeFamily, - mintRevisionTag, - (taggedChange) => - changeReceiver({ - ...taggedChange, - change: { changes: [{ type: "data", innerChange: taggedChange.change }] }, - }), - idCompressor, + super(modularChangeFamily, mintRevisionTag, (taggedChange) => + changeReceiver({ + ...taggedChange, + change: { changes: [{ type: "data", innerChange: taggedChange.change }] }, + }), ); this.schema = { diff --git a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts index b879efaef31b..d2fcba51fb04 100644 --- a/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/default-field-kinds/defaultChangeFamily.spec.ts @@ -7,6 +7,7 @@ import { strict as assert } from "node:assert"; import { type DeltaRoot, + EmptyKey, type FieldKey, type IForestSubscription, type JsonableTree, @@ -961,7 +962,8 @@ describe("DefaultEditBuilder", () => { const { builder, forest } = initializeEditableForest({ type: brand(JsonAsTree.Array.identifier), }); - builder.move({ parent: root, field: fooKey }, 0, 0, { parent: root, field: fooKey }, 0); + const sequencePath = { parent: root, field: EmptyKey }; + builder.move(sequencePath, 0, 0, sequencePath, 0); const treeView = toJsonableTreeFromForest(forest); const expected: JsonableTree = { type: brand(JsonAsTree.Array.identifier), diff --git a/packages/dds/tree/src/test/tree/visitDelta.spec.ts b/packages/dds/tree/src/test/tree/visitDelta.spec.ts index a76715a855ec..47776b0acb32 100644 --- a/packages/dds/tree/src/test/tree/visitDelta.spec.ts +++ b/packages/dds/tree/src/test/tree/visitDelta.spec.ts @@ -93,7 +93,7 @@ function testDeltaVisit( name === "create" ? ([ name, - (args[0] as ITreeCursorSynchronous[]).map(mapTreeFromCursor), + (args[0] as readonly ITreeCursorSynchronous[]).map(mapTreeFromCursor), args[1] as FieldKey, ] as VisitCall) : ([name, ...args] as VisitCall); @@ -135,6 +135,8 @@ const barKey: FieldKey = brand("bar"); const chunkX = chunkFromJsonField(["X"]); const chunkY = chunkFromJsonField(["Y"]); const chunkXY = chunkFromJsonField(["X", "Y"]); +const mapTreeX = chunkToMapTreeField(chunkX); +const mapTreeY = chunkToMapTreeField(chunkY); const field0: FieldKey = brand("-0"); const field1: FieldKey = brand("-1"); const field2: FieldKey = brand("-2"); @@ -161,7 +163,7 @@ describe("visitDelta", () => { fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], @@ -193,7 +195,7 @@ describe("visitDelta", () => { }, ]; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["enterField", rootKey], ["enterNode", 0], ["enterField", fooKey], @@ -291,7 +293,7 @@ describe("visitDelta", () => { fields: new Map([[rootKey, [{ count: 1, attach: { minor: 43 } }]]]), }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", field0], @@ -520,7 +522,7 @@ describe("visitDelta", () => { destroy: [{ id: detachId, count: 1 }], }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["enterField", field0], ["detach", { start: 0, end: 1 }, field1], ["exitField", field0], @@ -603,7 +605,7 @@ describe("visitDelta", () => { }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["enterField", rootKey], ["detach", { start: 0, end: 1 }, field1], ["enterNode", 0], @@ -643,8 +645,8 @@ describe("visitDelta", () => { }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], - ["create", chunkToMapTreeField(chunkY), field1], + ["create", mapTreeX, field0], + ["create", mapTreeY, field1], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], @@ -755,7 +757,7 @@ describe("visitDelta", () => { rename: [{ oldId: { minor: 42 }, count: 1, newId: { minor: 43 } }], }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["enterField", field0], ["detach", { start: 0, end: 1 }, field1], ["exitField", field0], @@ -782,7 +784,7 @@ describe("visitDelta", () => { rename: [{ oldId: buildId, count: 1, newId: detachId }], }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], // field0: buildId + ["create", mapTreeX, field0], // field0: buildId ["enterField", field0], ["enterNode", 0], ["enterField", barKey], @@ -976,7 +978,7 @@ describe("visitDelta", () => { rename: [renameOldNode, renameNewNode], }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field1], // field1: buildId + ["create", mapTreeX, field1], // field1: buildId ["enterField", field0], // field0: node1 ["detach", { start: 0, end: 1 }, field2], // field2: detachId ["exitField", field0], @@ -1004,7 +1006,7 @@ describe("visitDelta", () => { ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1031,7 +1033,7 @@ describe("visitDelta", () => { ["enterField", rootKey], ["enterNode", 0], ["enterField", fooKey], - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["attach", field0, 1, 0], ["exitField", fooKey], ["exitNode", 0], @@ -1057,7 +1059,7 @@ describe("visitDelta", () => { ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", chunkToMapTreeField(chunkY), field0], + ["create", mapTreeY, field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1070,8 +1072,8 @@ describe("visitDelta", () => { const refresherId = { minor: 42 }; const buildId = { minor: 43 }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], - ["create", chunkToMapTreeField(chunkX), field1], + ["create", mapTreeX, field0], + ["create", mapTreeX, field1], ["enterField", field1], ["enterNode", 0], ["enterField", fooKey], @@ -1136,7 +1138,7 @@ describe("visitDelta", () => { const delta: DeltaRoot = { build: [{ id: node, trees: chunkX }], }; - const expected: VisitScript = [["create", chunkToMapTreeField(chunkX), field0]]; + const expected: VisitScript = [["create", mapTreeX, field0]]; const revision = mintRevisionTag(); testDeltaVisit(delta, expected, index, revision); assert.deepEqual(Array.from(index.entries()), [ @@ -1204,8 +1206,8 @@ describe("visitDelta", () => { }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], - ["create", chunkToMapTreeField(chunkY), field1], + ["create", mapTreeX, field0], + ["create", mapTreeY, field1], ["enterField", rootKey], ["detach", { start: 0, end: 1 }, field2], ["detach", { start: 0, end: 1 }, field3], @@ -1241,7 +1243,7 @@ describe("visitDelta", () => { ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["attach", field0, 1, 0], ["exitField", rootKey], ]; @@ -1279,7 +1281,7 @@ describe("visitDelta", () => { fields: new Map([[rootKey, rootFieldDelta]]), }; const expected: VisitScript = [ - ["create", chunkToMapTreeField(chunkX), field0], + ["create", mapTreeX, field0], ["enterField", rootKey], ["exitField", rootKey], ["enterField", rootKey], From 4594c0667d5048b440a11af7e9c561b8c6386e81 Mon Sep 17 00:00:00 2001 From: Yann Achard Date: Fri, 7 Mar 2025 08:43:51 -0800 Subject: [PATCH 7/7] PR feedback part 1 --- packages/dds/tree/src/core/forest/forest.ts | 2 ++ packages/dds/tree/src/core/tree/visitDelta.ts | 5 +++++ .../chunked-forest/chunkTree.spec.ts | 13 +++++++++++++ 3 files changed, 20 insertions(+) diff --git a/packages/dds/tree/src/core/forest/forest.ts b/packages/dds/tree/src/core/forest/forest.ts index f64417fc37d0..0f76773b832c 100644 --- a/packages/dds/tree/src/core/forest/forest.ts +++ b/packages/dds/tree/src/core/forest/forest.ts @@ -89,6 +89,8 @@ export interface IForestSubscription { * * @remarks * Like {@link chunkField}, but forces the results into a single TreeChunk. + * While any TreeChunk is compatible with any forest, this method creates one optimized for this specific forest. + * The provided data must be compatible with the forest's current schema. */ chunkField(cursor: ITreeCursorSynchronous): TreeChunk; diff --git a/packages/dds/tree/src/core/tree/visitDelta.ts b/packages/dds/tree/src/core/tree/visitDelta.ts index e29fa12ac1b8..2cfd7c5fd0d4 100644 --- a/packages/dds/tree/src/core/tree/visitDelta.ts +++ b/packages/dds/tree/src/core/tree/visitDelta.ts @@ -596,6 +596,11 @@ function attachPass( } } +/** + * Converts a chunk of trees into an array of cursors. + * + * TODO: Update the visitDelta logic and downstream APIs to avoid splitting up sequences into individual nodes. + */ function nodeCursorsFromChunk(trees: TreeChunk): ITreeCursorSynchronous[] { return mapCursorField(trees.cursor(), (c) => c.fork()); } diff --git a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkTree.spec.ts b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkTree.spec.ts index 3fc50c52d324..24b008a10afe 100644 --- a/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkTree.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/chunked-forest/chunkTree.spec.ts @@ -19,6 +19,7 @@ import { type ChunkPolicy, type ShapeInfo, basicOnlyChunkPolicy, + chunkField, chunkRange, defaultChunkPolicy, insertValues, @@ -29,6 +30,8 @@ import { // eslint-disable-next-line import/no-internal-modules } from "../../../feature-libraries/chunked-forest/chunkTree.js"; // eslint-disable-next-line import/no-internal-modules +import { emptyChunk } from "../../../feature-libraries/chunked-forest/emptyChunk.js"; +// eslint-disable-next-line import/no-internal-modules import { SequenceChunk } from "../../../feature-libraries/chunked-forest/sequenceChunk.js"; import { TreeShape, @@ -328,6 +331,16 @@ describe("chunkTree", () => { }); }); + describe("chunkField", () => { + it("empty chunk", () => { + const chunks = chunkField(emptyChunk.cursor(), { + policy: defaultChunkPolicy, + idCompressor: undefined, + }); + assert.equal(chunks.length, 0); + }); + }); + describe("tryShapeFromSchema", () => { it("leaf", () => { const info = tryShapeFromSchema(