@fluidframework/tree 2.0.0-dev-rc.5.0.0.271717 → 2.0.0-dev-rc.5.0.0.272251

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/api-extractor/api-extractor-lint-beta.cjs.json +5 -0
  2. package/api-extractor/api-extractor-lint-beta.esm.json +5 -0
  3. package/api-extractor/api-extractor-lint-bundle.json +5 -0
  4. package/api-extractor/api-extractor-lint-public.cjs.json +5 -0
  5. package/api-extractor/api-extractor-lint-public.esm.json +5 -0
  6. package/dist/feature-libraries/index.d.ts +1 -1
  7. package/dist/feature-libraries/index.d.ts.map +1 -1
  8. package/dist/feature-libraries/index.js +2 -3
  9. package/dist/feature-libraries/index.js.map +1 -1
  10. package/dist/feature-libraries/modular-schema/fieldChangeHandler.d.ts +7 -7
  11. package/dist/feature-libraries/modular-schema/fieldChangeHandler.d.ts.map +1 -1
  12. package/dist/feature-libraries/modular-schema/fieldChangeHandler.js +6 -6
  13. package/dist/feature-libraries/modular-schema/fieldChangeHandler.js.map +1 -1
  14. package/dist/feature-libraries/modular-schema/index.d.ts +1 -1
  15. package/dist/feature-libraries/modular-schema/index.d.ts.map +1 -1
  16. package/dist/feature-libraries/modular-schema/index.js +2 -2
  17. package/dist/feature-libraries/modular-schema/index.js.map +1 -1
  18. package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts +2 -0
  19. package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  20. package/dist/feature-libraries/modular-schema/modularChangeFamily.js +37 -19
  21. package/dist/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  22. package/dist/feature-libraries/optional-field/optionalField.js +2 -2
  23. package/dist/feature-libraries/optional-field/optionalField.js.map +1 -1
  24. package/dist/feature-libraries/sequence-field/rebase.d.ts +2 -2
  25. package/dist/feature-libraries/sequence-field/rebase.d.ts.map +1 -1
  26. package/dist/feature-libraries/sequence-field/rebase.js +20 -14
  27. package/dist/feature-libraries/sequence-field/rebase.js.map +1 -1
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/packageVersion.js +1 -1
  30. package/dist/packageVersion.js.map +1 -1
  31. package/dist/shared-tree-core/branch.d.ts +18 -0
  32. package/dist/shared-tree-core/branch.d.ts.map +1 -1
  33. package/dist/shared-tree-core/branch.js +3 -0
  34. package/dist/shared-tree-core/branch.js.map +1 -1
  35. package/dist/shared-tree-core/branchCommitEnricher.d.ts +4 -2
  36. package/dist/shared-tree-core/branchCommitEnricher.d.ts.map +1 -1
  37. package/dist/shared-tree-core/branchCommitEnricher.js +11 -4
  38. package/dist/shared-tree-core/branchCommitEnricher.js.map +1 -1
  39. package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  40. package/dist/shared-tree-core/sharedTreeCore.js +9 -0
  41. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  42. package/dist/shared-tree-core/transactionEnricher.d.ts +11 -1
  43. package/dist/shared-tree-core/transactionEnricher.d.ts.map +1 -1
  44. package/dist/shared-tree-core/transactionEnricher.js +24 -1
  45. package/dist/shared-tree-core/transactionEnricher.js.map +1 -1
  46. package/lib/feature-libraries/index.d.ts +1 -1
  47. package/lib/feature-libraries/index.d.ts.map +1 -1
  48. package/lib/feature-libraries/index.js +1 -1
  49. package/lib/feature-libraries/index.js.map +1 -1
  50. package/lib/feature-libraries/modular-schema/fieldChangeHandler.d.ts +7 -7
  51. package/lib/feature-libraries/modular-schema/fieldChangeHandler.d.ts.map +1 -1
  52. package/lib/feature-libraries/modular-schema/fieldChangeHandler.js +5 -5
  53. package/lib/feature-libraries/modular-schema/fieldChangeHandler.js.map +1 -1
  54. package/lib/feature-libraries/modular-schema/index.d.ts +1 -1
  55. package/lib/feature-libraries/modular-schema/index.d.ts.map +1 -1
  56. package/lib/feature-libraries/modular-schema/index.js +1 -1
  57. package/lib/feature-libraries/modular-schema/index.js.map +1 -1
  58. package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts +2 -0
  59. package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  60. package/lib/feature-libraries/modular-schema/modularChangeFamily.js +38 -20
  61. package/lib/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  62. package/lib/feature-libraries/optional-field/optionalField.js +3 -3
  63. package/lib/feature-libraries/optional-field/optionalField.js.map +1 -1
  64. package/lib/feature-libraries/sequence-field/rebase.d.ts +2 -2
  65. package/lib/feature-libraries/sequence-field/rebase.d.ts.map +1 -1
  66. package/lib/feature-libraries/sequence-field/rebase.js +21 -15
  67. package/lib/feature-libraries/sequence-field/rebase.js.map +1 -1
  68. package/lib/packageVersion.d.ts +1 -1
  69. package/lib/packageVersion.js +1 -1
  70. package/lib/packageVersion.js.map +1 -1
  71. package/lib/shared-tree-core/branch.d.ts +18 -0
  72. package/lib/shared-tree-core/branch.d.ts.map +1 -1
  73. package/lib/shared-tree-core/branch.js +3 -0
  74. package/lib/shared-tree-core/branch.js.map +1 -1
  75. package/lib/shared-tree-core/branchCommitEnricher.d.ts +4 -2
  76. package/lib/shared-tree-core/branchCommitEnricher.d.ts.map +1 -1
  77. package/lib/shared-tree-core/branchCommitEnricher.js +11 -4
  78. package/lib/shared-tree-core/branchCommitEnricher.js.map +1 -1
  79. package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  80. package/lib/shared-tree-core/sharedTreeCore.js +9 -0
  81. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  82. package/lib/shared-tree-core/transactionEnricher.d.ts +11 -1
  83. package/lib/shared-tree-core/transactionEnricher.d.ts.map +1 -1
  84. package/lib/shared-tree-core/transactionEnricher.js +24 -1
  85. package/lib/shared-tree-core/transactionEnricher.js.map +1 -1
  86. package/package.json +27 -24
  87. package/src/feature-libraries/index.ts +0 -1
  88. package/src/feature-libraries/modular-schema/fieldChangeHandler.ts +6 -7
  89. package/src/feature-libraries/modular-schema/index.ts +1 -1
  90. package/src/feature-libraries/modular-schema/modularChangeFamily.ts +55 -23
  91. package/src/feature-libraries/optional-field/optionalField.ts +3 -3
  92. package/src/feature-libraries/sequence-field/rebase.ts +15 -33
  93. package/src/packageVersion.ts +1 -1
  94. package/src/shared-tree-core/branch.ts +24 -0
  95. package/src/shared-tree-core/branchCommitEnricher.ts +15 -6
  96. package/src/shared-tree-core/sharedTreeCore.ts +9 -0
  97. package/src/shared-tree-core/transactionEnricher.ts +27 -1
  98. package/tsdoc.json +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/tree",
3
- "version": "2.0.0-dev-rc.5.0.0.271717",
3
+ "version": "2.0.0-dev-rc.5.0.0.272251",
4
4
  "description": "Distributed tree",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -67,17 +67,17 @@
67
67
  "temp-directory": "nyc/.nyc_output"
68
68
  },
69
69
  "dependencies": {
70
- "@fluid-internal/client-utils": "2.0.0-dev-rc.5.0.0.271717",
71
- "@fluidframework/container-runtime": "2.0.0-dev-rc.5.0.0.271717",
72
- "@fluidframework/core-interfaces": "2.0.0-dev-rc.5.0.0.271717",
73
- "@fluidframework/core-utils": "2.0.0-dev-rc.5.0.0.271717",
74
- "@fluidframework/datastore-definitions": "2.0.0-dev-rc.5.0.0.271717",
75
- "@fluidframework/driver-definitions": "2.0.0-dev-rc.5.0.0.271717",
76
- "@fluidframework/id-compressor": "2.0.0-dev-rc.5.0.0.271717",
77
- "@fluidframework/runtime-definitions": "2.0.0-dev-rc.5.0.0.271717",
78
- "@fluidframework/runtime-utils": "2.0.0-dev-rc.5.0.0.271717",
79
- "@fluidframework/shared-object-base": "2.0.0-dev-rc.5.0.0.271717",
80
- "@fluidframework/telemetry-utils": "2.0.0-dev-rc.5.0.0.271717",
70
+ "@fluid-internal/client-utils": "2.0.0-dev-rc.5.0.0.272251",
71
+ "@fluidframework/container-runtime": "2.0.0-dev-rc.5.0.0.272251",
72
+ "@fluidframework/core-interfaces": "2.0.0-dev-rc.5.0.0.272251",
73
+ "@fluidframework/core-utils": "2.0.0-dev-rc.5.0.0.272251",
74
+ "@fluidframework/datastore-definitions": "2.0.0-dev-rc.5.0.0.272251",
75
+ "@fluidframework/driver-definitions": "2.0.0-dev-rc.5.0.0.272251",
76
+ "@fluidframework/id-compressor": "2.0.0-dev-rc.5.0.0.272251",
77
+ "@fluidframework/runtime-definitions": "2.0.0-dev-rc.5.0.0.272251",
78
+ "@fluidframework/runtime-utils": "2.0.0-dev-rc.5.0.0.272251",
79
+ "@fluidframework/shared-object-base": "2.0.0-dev-rc.5.0.0.272251",
80
+ "@fluidframework/telemetry-utils": "2.0.0-dev-rc.5.0.0.272251",
81
81
  "@sinclair/typebox": "^0.32.29",
82
82
  "@tylerbu/sorted-btree-es6": "^1.8.0",
83
83
  "@ungap/structured-clone": "^1.2.0",
@@ -86,19 +86,19 @@
86
86
  "devDependencies": {
87
87
  "@arethetypeswrong/cli": "^0.15.2",
88
88
  "@biomejs/biome": "^1.7.3",
89
- "@fluid-internal/mocha-test-setup": "2.0.0-dev-rc.5.0.0.271717",
90
- "@fluid-private/stochastic-test-utils": "2.0.0-dev-rc.5.0.0.271717",
91
- "@fluid-private/test-dds-utils": "2.0.0-dev-rc.5.0.0.271717",
92
- "@fluid-private/test-drivers": "2.0.0-dev-rc.5.0.0.271717",
89
+ "@fluid-internal/mocha-test-setup": "2.0.0-dev-rc.5.0.0.272251",
90
+ "@fluid-private/stochastic-test-utils": "2.0.0-dev-rc.5.0.0.272251",
91
+ "@fluid-private/test-dds-utils": "2.0.0-dev-rc.5.0.0.272251",
92
+ "@fluid-private/test-drivers": "2.0.0-dev-rc.5.0.0.272251",
93
93
  "@fluid-tools/benchmark": "^0.48.0",
94
94
  "@fluid-tools/build-cli": "^0.39.0",
95
95
  "@fluidframework/build-common": "^2.0.3",
96
96
  "@fluidframework/build-tools": "^0.39.0",
97
- "@fluidframework/container-definitions": "2.0.0-dev-rc.5.0.0.271717",
98
- "@fluidframework/container-loader": "2.0.0-dev-rc.5.0.0.271717",
97
+ "@fluidframework/container-definitions": "2.0.0-dev-rc.5.0.0.272251",
98
+ "@fluidframework/container-loader": "2.0.0-dev-rc.5.0.0.272251",
99
99
  "@fluidframework/eslint-config-fluid": "^5.3.0",
100
- "@fluidframework/test-runtime-utils": "2.0.0-dev-rc.5.0.0.271717",
101
- "@fluidframework/test-utils": "2.0.0-dev-rc.5.0.0.271717",
100
+ "@fluidframework/test-runtime-utils": "2.0.0-dev-rc.5.0.0.272251",
101
+ "@fluidframework/test-utils": "2.0.0-dev-rc.5.0.0.272251",
102
102
  "@microsoft/api-extractor": "^7.45.1",
103
103
  "@types/diff": "^3.5.1",
104
104
  "@types/easy-table": "^0.0.32",
@@ -109,6 +109,7 @@
109
109
  "ajv": "^8.12.0",
110
110
  "ajv-formats": "^3.0.1",
111
111
  "c8": "^8.0.1",
112
+ "concurrently": "^8.2.1",
112
113
  "copyfiles": "^2.4.1",
113
114
  "cross-env": "^7.0.3",
114
115
  "dependency-cruiser": "^14.1.0",
@@ -134,9 +135,6 @@
134
135
  "...",
135
136
  "@fluidframework/id-compressor#build:test:esm"
136
137
  ],
137
- "check:release-tags": [
138
- "build:esnext"
139
- ],
140
138
  "ci:build:docs": [
141
139
  "build:esnext"
142
140
  ]
@@ -163,9 +161,14 @@
163
161
  "build:test:esm": "tsc --project ./src/test/tsconfig.json",
164
162
  "check:are-the-types-wrong": "attw --pack .",
165
163
  "check:biome": "biome check . --formatter-enabled=true",
164
+ "check:exports": "concurrently \"npm:check:exports:*\"",
165
+ "check:exports:bundle-release-tags": "api-extractor run --config api-extractor/api-extractor-lint-bundle.json",
166
+ "check:exports:cjs:beta": "api-extractor run --config api-extractor/api-extractor-lint-beta.cjs.json",
167
+ "check:exports:cjs:public": "api-extractor run --config api-extractor/api-extractor-lint-public.cjs.json",
168
+ "check:exports:esm:beta": "api-extractor run --config api-extractor/api-extractor-lint-beta.esm.json",
169
+ "check:exports:esm:public": "api-extractor run --config api-extractor/api-extractor-lint-public.esm.json",
166
170
  "check:format": "npm run check:prettier",
167
171
  "check:prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore",
168
- "check:release-tags": "api-extractor run --local --config ./api-extractor-lint.json",
169
172
  "ci:build:docs": "api-extractor run",
170
173
  "clean": "rimraf --glob dist lib \"*.d.ts\" \"**/*.tsbuildinfo\" \"**/*.build.log\" _api-extractor-temp nyc",
171
174
  "depcruise": "depcruise src/ --ignore-known",
@@ -117,7 +117,6 @@ export {
117
117
  genericFieldKind,
118
118
  type HasFieldChanges,
119
119
  type NodeExistsConstraint,
120
- NodeExistenceState,
121
120
  FieldKindWithEditor,
122
121
  ModularChangeFamily,
123
122
  type RelevantRemovedRootsFromChild,
@@ -124,7 +124,6 @@ export interface FieldChangeRebaser<TChangeset> {
124
124
  genId: IdAllocator,
125
125
  crossFieldManager: CrossFieldManager,
126
126
  revisionMetadata: RebaseRevisionMetadata,
127
- existenceState?: NodeExistenceState,
128
127
  ): TChangeset;
129
128
 
130
129
  /**
@@ -189,9 +188,9 @@ export type NodeChangeInverter = (change: NodeId) => NodeId;
189
188
  /**
190
189
  * @internal
191
190
  */
192
- export enum NodeExistenceState {
193
- Alive,
194
- Dead,
191
+ export enum NodeAttachState {
192
+ Attached,
193
+ Detached,
195
194
  }
196
195
 
197
196
  /**
@@ -201,10 +200,10 @@ export type NodeChangeRebaser = (
201
200
  change: NodeId | undefined,
202
201
  baseChange: NodeId | undefined,
203
202
  /**
204
- * Whether or not the node is alive or dead in the input context of change.
205
- * Defaults to Alive if undefined.
203
+ * Whether the node is attached to this field in the output context of the base change.
204
+ * Defaults to attached if undefined.
206
205
  */
207
- state?: NodeExistenceState,
206
+ state?: NodeAttachState,
208
207
  ) => NodeId | undefined;
209
208
 
210
209
  /**
@@ -39,7 +39,7 @@ export {
39
39
  type RebaseRevisionMetadata,
40
40
  type RelevantRemovedRootsFromChild,
41
41
  type ToDelta,
42
- NodeExistenceState,
42
+ NodeAttachState,
43
43
  type FieldChangeEncodingContext,
44
44
  } from "./fieldChangeHandler.js";
45
45
  export type {
@@ -77,7 +77,7 @@ import {
77
77
  } from "./crossFieldQueries.js";
78
78
  import {
79
79
  type FieldChangeHandler,
80
- NodeExistenceState,
80
+ NodeAttachState,
81
81
  type RebaseRevisionMetadata,
82
82
  } from "./fieldChangeHandler.js";
83
83
  import { type FieldKindWithEditor, withEditor } from "./fieldKindWithEditor.js";
@@ -639,7 +639,7 @@ export class ModularChangeFamily
639
639
  );
640
640
 
641
641
  const rebasedNodes: ChangeAtomIdMap<NodeChangeset> = new Map();
642
- for (const [newId, baseId, existenceState] of crossFieldTable.nodeIdPairs) {
642
+ for (const [newId, baseId] of crossFieldTable.nodeIdPairs) {
643
643
  const newNodeChange =
644
644
  newId !== undefined
645
645
  ? tryGetFromNestedMap(change.nodeChanges, newId.revision, newId.localId)
@@ -660,8 +660,6 @@ export class ModularChangeFamily
660
660
  genId,
661
661
  crossFieldTable,
662
662
  rebaseMetadata,
663
- constraintState,
664
- existenceState,
665
663
  );
666
664
 
667
665
  if (rebasedNode !== undefined) {
@@ -700,6 +698,13 @@ export class ModularChangeFamily
700
698
  }
701
699
  }
702
700
 
701
+ this.updateConstraintsForFields(
702
+ rebasedFields,
703
+ NodeAttachState.Attached,
704
+ constraintState,
705
+ rebasedNodes,
706
+ );
707
+
703
708
  return makeModularChangeset(
704
709
  this.pruneFieldMap(rebasedFields, rebasedNodes),
705
710
  rebasedNodes,
@@ -718,7 +723,6 @@ export class ModularChangeFamily
718
723
  genId: IdAllocator,
719
724
  crossFieldTable: RebaseTable,
720
725
  revisionMetadata: RebaseRevisionMetadata,
721
- existenceState: NodeExistenceState = NodeExistenceState.Alive,
722
726
  ): FieldChangeMap {
723
727
  const rebasedFields: FieldChangeMap = new Map();
724
728
 
@@ -739,9 +743,9 @@ export class ModularChangeFamily
739
743
  const rebaseChild = (
740
744
  child: NodeId | undefined,
741
745
  baseChild: NodeId | undefined,
742
- stateChange: NodeExistenceState | undefined,
746
+ _attachState: NodeAttachState | undefined,
743
747
  ): ChangeAtomId => {
744
- crossFieldTable.nodeIdPairs.push([child, baseChild, stateChange]);
748
+ crossFieldTable.nodeIdPairs.push([child, baseChild]);
745
749
  return (
746
750
  child ??
747
751
  // The fact `child` is undefined means that the changeset to rebase does not include changes for
@@ -801,13 +805,13 @@ export class ModularChangeFamily
801
805
  0x5b6 /* This field should not have any base changes */,
802
806
  );
803
807
 
804
- crossFieldTable.nodeIdPairs.push([child, undefined, existenceState]);
808
+ crossFieldTable.nodeIdPairs.push([child, undefined]);
809
+
805
810
  return child;
806
811
  },
807
812
  genId,
808
813
  manager,
809
814
  revisionMetadata,
810
- existenceState,
811
815
  );
812
816
  const rebasedFieldChange: FieldChange = {
813
817
  fieldKind: fieldKind.identifier,
@@ -826,8 +830,6 @@ export class ModularChangeFamily
826
830
  genId: IdAllocator,
827
831
  crossFieldTable: RebaseTable,
828
832
  revisionMetadata: RebaseRevisionMetadata,
829
- constraintState: ConstraintState,
830
- existenceState: NodeExistenceState = NodeExistenceState.Alive,
831
833
  ): NodeChangeset | undefined {
832
834
  const key = change ?? over;
833
835
  if (key === undefined) {
@@ -842,7 +844,6 @@ export class ModularChangeFamily
842
844
  genId,
843
845
  crossFieldTable,
844
846
  revisionMetadata,
845
- existenceState,
846
847
  );
847
848
 
848
849
  const rebasedChange: NodeChangeset = {};
@@ -855,21 +856,52 @@ export class ModularChangeFamily
855
856
  rebasedChange.nodeExistsConstraint = change.nodeExistsConstraint;
856
857
  }
857
858
 
858
- // If there's a node exists constraint and we removed or revived the node, update constraint state
859
- if (rebasedChange.nodeExistsConstraint !== undefined) {
860
- const violatedAfter = existenceState === NodeExistenceState.Dead;
859
+ crossFieldTable.rebasedNodeCache.set(key, rebasedChange);
860
+ return rebasedChange;
861
+ }
862
+
863
+ private updateConstraintsForFields(
864
+ fields: FieldChangeMap,
865
+ parentAttachState: NodeAttachState,
866
+ constraintState: ConstraintState,
867
+ nodes: ChangeAtomIdMap<NodeChangeset>,
868
+ ): void {
869
+ for (const field of fields.values()) {
870
+ const handler = getChangeHandler(this.fieldKinds, field.fieldKind);
871
+ for (const [nodeId, index] of handler.getNestedChanges(field.change)) {
872
+ const isDetached = index === undefined;
873
+ const attachState =
874
+ parentAttachState === NodeAttachState.Detached || isDetached
875
+ ? NodeAttachState.Detached
876
+ : NodeAttachState.Attached;
877
+ this.updateConstraintsForNode(nodeId, attachState, constraintState, nodes);
878
+ }
879
+ }
880
+ }
861
881
 
862
- if (rebasedChange.nodeExistsConstraint.violated !== violatedAfter) {
863
- rebasedChange.nodeExistsConstraint = {
864
- ...rebasedChange.nodeExistsConstraint,
865
- violated: violatedAfter,
882
+ private updateConstraintsForNode(
883
+ nodeId: NodeId,
884
+ attachState: NodeAttachState,
885
+ constraintState: ConstraintState,
886
+ nodes: ChangeAtomIdMap<NodeChangeset>,
887
+ ): void {
888
+ const node =
889
+ tryGetFromNestedMap(nodes, nodeId.revision, nodeId.localId) ?? fail("Unknown node ID");
890
+
891
+ if (node.nodeExistsConstraint !== undefined) {
892
+ const isNowViolated = attachState === NodeAttachState.Detached;
893
+ if (node.nodeExistsConstraint.violated !== isNowViolated) {
894
+ node.nodeExistsConstraint = {
895
+ ...node.nodeExistsConstraint,
896
+ violated: isNowViolated,
866
897
  };
867
- constraintState.violationCount += violatedAfter ? 1 : -1;
898
+ constraintState.violationCount += isNowViolated ? 1 : -1;
868
899
  }
869
900
  }
870
901
 
871
- crossFieldTable.rebasedNodeCache.set(key, rebasedChange);
872
- return rebasedChange;
902
+ if (node.fieldChanges !== undefined) {
903
+ this.updateConstraintsForFields(node.fieldChanges, attachState, constraintState, nodes);
904
+ }
873
905
  }
874
906
 
875
907
  private pruneFieldMap(
@@ -1475,7 +1507,7 @@ interface RebaseTable extends CrossFieldTable<FieldChange> {
1475
1507
  /**
1476
1508
  * List of (newId, baseId) pairs encountered so far.
1477
1509
  */
1478
- nodeIdPairs: [NodeId | undefined, NodeId | undefined, NodeExistenceState | undefined][];
1510
+ nodeIdPairs: [NodeId | undefined, NodeId | undefined][];
1479
1511
  }
1480
1512
 
1481
1513
  interface RebaseFieldContext {
@@ -35,7 +35,7 @@ import {
35
35
  type NodeChangeComposer,
36
36
  type NodeChangePruner,
37
37
  type NodeChangeRebaser,
38
- NodeExistenceState,
38
+ NodeAttachState,
39
39
  type NodeId,
40
40
  type RelevantRemovedRootsFromChild,
41
41
  type ToDelta,
@@ -373,7 +373,7 @@ export const optionalChangeRebaser: FieldChangeRebaser<OptionalChangeset> = {
373
373
  const rebasedChildChange = rebaseChild(
374
374
  childChange,
375
375
  overChildChange,
376
- rebasedId === "self" ? NodeExistenceState.Alive : NodeExistenceState.Dead,
376
+ rebasedId === "self" ? NodeAttachState.Attached : NodeAttachState.Detached,
377
377
  );
378
378
  if (rebasedChildChange !== undefined) {
379
379
  rebasedChildChanges.push([rebasedId, rebasedChildChange]);
@@ -385,7 +385,7 @@ export const optionalChangeRebaser: FieldChangeRebaser<OptionalChangeset> = {
385
385
  const rebasedChildChange = rebaseChild(
386
386
  undefined,
387
387
  overChildChange,
388
- rebasedId === "self" ? NodeExistenceState.Alive : NodeExistenceState.Dead,
388
+ rebasedId === "self" ? NodeAttachState.Attached : NodeAttachState.Detached,
389
389
  );
390
390
  if (rebasedChildChange !== undefined) {
391
391
  rebasedChildChanges.push([rebasedId, rebasedChildChange]);
@@ -11,7 +11,7 @@ import {
11
11
  type CrossFieldManager,
12
12
  CrossFieldTarget,
13
13
  type NodeChangeRebaser,
14
- NodeExistenceState,
14
+ NodeAttachState,
15
15
  type NodeId,
16
16
  type RebaseRevisionMetadata,
17
17
  } from "../modular-schema/index.js";
@@ -74,7 +74,6 @@ export function rebase(
74
74
  genId: IdAllocator,
75
75
  manager: CrossFieldManager,
76
76
  revisionMetadata: RebaseRevisionMetadata,
77
- nodeExistenceState: NodeExistenceState = NodeExistenceState.Alive,
78
77
  ): Changeset {
79
78
  return rebaseMarkList(
80
79
  change,
@@ -83,7 +82,6 @@ export function rebase(
83
82
  rebaseChild,
84
83
  genId,
85
84
  manager as MoveEffectTable,
86
- nodeExistenceState,
87
85
  );
88
86
  }
89
87
 
@@ -94,20 +92,13 @@ function rebaseMarkList(
94
92
  rebaseChild: NodeChangeRebaser,
95
93
  genId: IdAllocator,
96
94
  moveEffects: CrossFieldManager<MoveEffect>,
97
- nodeExistenceState: NodeExistenceState,
98
95
  ): MarkList {
99
96
  const factory = new MarkListFactory();
100
97
  const queue = new RebaseQueue(baseMarkList, currMarkList, metadata, moveEffects);
101
98
 
102
99
  while (!queue.isEmpty()) {
103
100
  const { baseMark, newMark: currMark } = queue.pop();
104
- const rebasedMark = rebaseMark(
105
- currMark,
106
- baseMark,
107
- rebaseChild,
108
- moveEffects,
109
- nodeExistenceState,
110
- );
101
+ const rebasedMark = rebaseMark(currMark, baseMark, rebaseChild, moveEffects);
111
102
  factory.push(rebasedMark);
112
103
  }
113
104
 
@@ -272,7 +263,6 @@ function rebaseMark(
272
263
  baseMark: Mark,
273
264
  rebaseChild: NodeChangeRebaser,
274
265
  moveEffects: MoveEffectTable,
275
- nodeExistenceState: NodeExistenceState,
276
266
  ): Mark {
277
267
  const rebasedMark = rebaseNodeChange(cloneMark(currMark), baseMark, rebaseChild);
278
268
  const movedNodeChanges = getMovedChangesFromBaseMark(moveEffects, baseMark);
@@ -284,15 +274,10 @@ function rebaseMark(
284
274
  rebasedMark.changes = movedNodeChanges;
285
275
  }
286
276
 
287
- return rebaseMarkIgnoreChild(rebasedMark, baseMark, moveEffects, nodeExistenceState);
277
+ return rebaseMarkIgnoreChild(rebasedMark, baseMark, moveEffects);
288
278
  }
289
279
 
290
- function rebaseMarkIgnoreChild(
291
- currMark: Mark,
292
- baseMark: Mark,
293
- moveEffects: MoveEffectTable,
294
- nodeExistenceState: NodeExistenceState,
295
- ): Mark {
280
+ function rebaseMarkIgnoreChild(currMark: Mark, baseMark: Mark, moveEffects: MoveEffectTable): Mark {
296
281
  let rebasedMark: Mark;
297
282
  if (isDetach(baseMark)) {
298
283
  if (baseMark.cellId !== undefined) {
@@ -341,13 +326,11 @@ function rebaseMarkIgnoreChild(
341
326
  currMark,
342
327
  { ...baseMark.attach, cellId: cloneCellId(baseMark.cellId), count: baseMark.count },
343
328
  moveEffects,
344
- nodeExistenceState,
345
329
  );
346
330
  rebasedMark = rebaseMarkIgnoreChild(
347
331
  halfRebasedMark,
348
332
  { ...baseMark.detach, count: baseMark.count },
349
333
  moveEffects,
350
- nodeExistenceState,
351
334
  );
352
335
  } else {
353
336
  rebasedMark = currMark;
@@ -465,19 +448,18 @@ function rebaseNodeChange(currMark: Mark, baseMark: Mark, nodeRebaser: NodeChang
465
448
  return currMark;
466
449
  }
467
450
 
468
- if (markEmptiesCells(baseMark) && !isMoveMark(baseMark)) {
469
- return withNodeChange(
470
- currMark,
471
- nodeRebaser(currChange, baseChange, NodeExistenceState.Dead),
472
- );
473
- } else if (markFillsCells(baseMark) && !isMoveMark(baseMark)) {
474
- return withNodeChange(
475
- currMark,
476
- nodeRebaser(currChange, baseChange, NodeExistenceState.Alive),
477
- );
478
- }
451
+ const nodeState = nodeStateAfterMark(baseMark);
452
+ return withNodeChange(currMark, nodeRebaser(currChange, baseChange, nodeState));
453
+ }
479
454
 
480
- return withNodeChange(currMark, nodeRebaser(currChange, baseChange));
455
+ function nodeStateAfterMark(mark: Mark): NodeAttachState {
456
+ if (markEmptiesCells(mark)) {
457
+ return NodeAttachState.Detached;
458
+ } else if (markFillsCells(mark)) {
459
+ return NodeAttachState.Attached;
460
+ } else {
461
+ return mark.cellId === undefined ? NodeAttachState.Attached : NodeAttachState.Detached;
462
+ }
481
463
  }
482
464
 
483
465
  function makeDetachedMark(mark: Mark, cellId: ChangeAtomId): Mark {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/tree";
9
- export const pkgVersion = "2.0.0-dev-rc.5.0.0.271717";
9
+ export const pkgVersion = "2.0.0-dev-rc.5.0.0.272251";
@@ -120,6 +120,27 @@ export interface SharedTreeBranchEvents<TEditor extends ChangeFamilyEditor, TCha
120
120
  * Fired after this branch is disposed
121
121
  */
122
122
  dispose(): void;
123
+
124
+ /**
125
+ * Fired after a new transaction is started.
126
+ * @param isOuterTransaction - true iff the transaction being started is the outermost transaction
127
+ * as opposed to a nested transaction.
128
+ */
129
+ transactionStarted(isOuterTransaction: boolean): void;
130
+
131
+ /**
132
+ * Fired after the current transaction is aborted.
133
+ * @param isOuterTransaction - true iff the transaction being aborted is the outermost transaction
134
+ * as opposed to a nested transaction.
135
+ */
136
+ transactionAborted(isOuterTransaction: boolean): void;
137
+
138
+ /**
139
+ * Fired after the current transaction is committed.
140
+ * @param isOuterTransaction - true iff the transaction being committed is the outermost transaction
141
+ * as opposed to a nested transaction.
142
+ */
143
+ transactionCommitted(isOuterTransaction: boolean): void;
123
144
  }
124
145
 
125
146
  /**
@@ -267,6 +288,7 @@ export class SharedTreeBranch<TEditor extends ChangeFamilyEditor, TChange> exten
267
288
  onForkUnSubscribe();
268
289
  });
269
290
  this.editor.enterTransaction();
291
+ this.emit("transactionStarted", this.transactions.size === 1);
270
292
  }
271
293
 
272
294
  /**
@@ -283,6 +305,7 @@ export class SharedTreeBranch<TEditor extends ChangeFamilyEditor, TChange> exten
283
305
  const [startCommit, commits] = this.popTransaction();
284
306
  this.editor.exitTransaction();
285
307
 
308
+ this.emit("transactionCommitted", this.transactions.size === 0);
286
309
  if (commits.length === 0) {
287
310
  return undefined;
288
311
  }
@@ -329,6 +352,7 @@ export class SharedTreeBranch<TEditor extends ChangeFamilyEditor, TChange> exten
329
352
  const [startCommit, commits] = this.popTransaction();
330
353
  this.editor.exitTransaction();
331
354
 
355
+ this.emit("transactionAborted", this.transactions.size === 0);
332
356
  if (commits.length === 0) {
333
357
  return [undefined, []];
334
358
  }
@@ -12,8 +12,7 @@ import { TransactionEnricher } from "./transactionEnricher.js";
12
12
  * Utility for enriching commits from a {@link Branch} before these commits are applied and submitted.
13
13
  */
14
14
  export class BranchCommitEnricher<TChange> {
15
- private transactionEnricher?: TransactionEnricher<TChange>;
16
- private readonly rebaser: ChangeRebaser<TChange>;
15
+ private readonly transactionEnricher: TransactionEnricher<TChange>;
17
16
  private readonly enricher: ChangeEnricherReadonlyCheckout<TChange>;
18
17
  /**
19
18
  * Maps each local commit to the corresponding enriched commit.
@@ -28,8 +27,8 @@ export class BranchCommitEnricher<TChange> {
28
27
  rebaser: ChangeRebaser<TChange>,
29
28
  enricher: ChangeEnricherReadonlyCheckout<TChange>,
30
29
  ) {
31
- this.rebaser = rebaser;
32
30
  this.enricher = enricher;
31
+ this.transactionEnricher = new TransactionEnricher(rebaser, this.enricher);
33
32
  }
34
33
 
35
34
  /**
@@ -39,6 +38,18 @@ export class BranchCommitEnricher<TChange> {
39
38
  return this.preparedCommits.size;
40
39
  }
41
40
 
41
+ public startNewTransaction(): void {
42
+ this.transactionEnricher.startNewTransaction();
43
+ }
44
+
45
+ public commitCurrentTransaction(): void {
46
+ this.transactionEnricher.commitCurrentTransaction();
47
+ }
48
+
49
+ public abortCurrentTransaction(): void {
50
+ this.transactionEnricher.abortCurrentTransaction();
51
+ }
52
+
42
53
  /**
43
54
  * Adds a commit to the enricher.
44
55
  * @param commit - A commit that is part of a transaction.
@@ -46,8 +57,7 @@ export class BranchCommitEnricher<TChange> {
46
57
  public ingestTransactionCommit(commit: GraphCommit<TChange>): void {
47
58
  // We do not submit ops for changes that are part of a transaction.
48
59
  // But we need to enrich the commits that will be sent if the transaction is committed.
49
- this.transactionEnricher ??= new TransactionEnricher(this.rebaser, this.enricher);
50
- this.transactionEnricher.addTransactionSteps(commit);
60
+ this.transactionEnricher.addTransactionStep(commit);
51
61
  }
52
62
 
53
63
  /**
@@ -66,7 +76,6 @@ export class BranchCommitEnricher<TChange> {
66
76
  "Unexpected transaction commit without transaction steps",
67
77
  );
68
78
  enrichedChange = this.transactionEnricher.getComposedChange(commit.revision);
69
- delete this.transactionEnricher;
70
79
  } else {
71
80
  enrichedChange = this.enricher.updateChangeEnrichments(commit.change, commit.revision);
72
81
  }
@@ -151,6 +151,15 @@ export class SharedTreeCore<TEditor extends ChangeFamilyEditor, TChange> extends
151
151
  */
152
152
  const localSessionId = runtime.idCompressor.localSessionId;
153
153
  this.editManager = new EditManager(changeFamily, localSessionId, this.mintRevisionTag);
154
+ this.editManager.localBranch.on("transactionStarted", () => {
155
+ this.commitEnricher.startNewTransaction();
156
+ });
157
+ this.editManager.localBranch.on("transactionAborted", () => {
158
+ this.commitEnricher.abortCurrentTransaction();
159
+ });
160
+ this.editManager.localBranch.on("transactionCommitted", () => {
161
+ this.commitEnricher.commitCurrentTransaction();
162
+ });
154
163
  this.editManager.localBranch.on("beforeChange", (change) => {
155
164
  // Ensure that any previously prepared commits that have not been sent are purged.
156
165
  this.commitEnricher.purgePreparedCommits();
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { assert } from "@fluidframework/core-utils/internal";
6
7
  import type { ChangeRebaser, GraphCommit, RevisionTag } from "../core/index.js";
7
8
  import type { ChangeEnricherReadonlyCheckout } from "./changeEnricher.js";
8
9
 
@@ -13,6 +14,13 @@ export class TransactionEnricher<TChange> {
13
14
  private readonly rebaser: ChangeRebaser<TChange>;
14
15
  private readonly enricher: ChangeEnricherReadonlyCheckout<TChange>;
15
16
  private readonly transactionCommits: GraphCommit<TChange>[] = [];
17
+ /**
18
+ * The number of commits before the start of each active transaction scope.
19
+ * For a stack of `n` transaction scopes, the array will contain `n` integers,
20
+ * where the integer at index `i` is the number of commits made before the start of the `i`th transaction scope
21
+ * (therefore, the first element in the array, if present, is always 0)
22
+ */
23
+ private readonly transactionScopesStart: number[] = [];
16
24
 
17
25
  public constructor(
18
26
  rebaser: ChangeRebaser<TChange>,
@@ -22,14 +30,32 @@ export class TransactionEnricher<TChange> {
22
30
  this.enricher = enricher;
23
31
  }
24
32
 
25
- public addTransactionSteps(commit: GraphCommit<TChange>): void {
33
+ public startNewTransaction(): void {
34
+ this.transactionScopesStart.push(this.transactionCommits.length);
35
+ }
36
+
37
+ public commitCurrentTransaction(): void {
38
+ const commitsCommitted = this.transactionScopesStart.pop();
39
+ assert(commitsCommitted !== undefined, "No transaction to commit");
40
+ }
41
+
42
+ public abortCurrentTransaction(): void {
43
+ const scopeStart = this.transactionScopesStart.pop();
44
+ assert(scopeStart !== undefined, "No transaction to abort");
45
+ this.transactionCommits.length = scopeStart;
46
+ }
47
+
48
+ public addTransactionStep(commit: GraphCommit<TChange>): void {
49
+ assert(this.transactionScopesStart.length !== 0, "No transaction to add a step to");
26
50
  const change = this.enricher.updateChangeEnrichments(commit.change, commit.revision);
27
51
  this.transactionCommits.push({ ...commit, change });
28
52
  }
29
53
 
30
54
  public getComposedChange(revision: RevisionTag): TChange {
55
+ assert(this.transactionScopesStart.length === 0, "Transaction not committed");
31
56
  const squashed = this.rebaser.compose(this.transactionCommits);
32
57
  const tagged = this.rebaser.changeRevision(squashed, revision);
58
+ this.transactionCommits.length = 0;
33
59
  return tagged;
34
60
  }
35
61
  }
package/tsdoc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
3
+ "extends": ["../../../common/build/build-common/tsdoc-base.json"]
4
+ }