@fluidframework/tree 2.10.0-306579 → 2.10.0-307060

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 (110) hide show
  1. package/api-report/tree.alpha.api.md +11 -9
  2. package/api-report/tree.beta.api.md +2 -0
  3. package/api-report/tree.legacy.alpha.api.md +2 -0
  4. package/api-report/tree.legacy.public.api.md +2 -0
  5. package/api-report/tree.public.api.md +2 -0
  6. package/dist/feature-libraries/chunked-forest/basicChunk.d.ts +26 -5
  7. package/dist/feature-libraries/chunked-forest/basicChunk.d.ts.map +1 -1
  8. package/dist/feature-libraries/chunked-forest/basicChunk.js +15 -5
  9. package/dist/feature-libraries/chunked-forest/basicChunk.js.map +1 -1
  10. package/dist/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
  11. package/dist/feature-libraries/chunked-forest/chunkedForest.js +5 -0
  12. package/dist/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
  13. package/dist/feature-libraries/index.d.ts +1 -1
  14. package/dist/feature-libraries/index.d.ts.map +1 -1
  15. package/dist/feature-libraries/index.js +2 -2
  16. package/dist/feature-libraries/index.js.map +1 -1
  17. package/dist/feature-libraries/modular-schema/discrepancies.d.ts +27 -27
  18. package/dist/feature-libraries/modular-schema/discrepancies.d.ts.map +1 -1
  19. package/dist/feature-libraries/modular-schema/discrepancies.js +33 -33
  20. package/dist/feature-libraries/modular-schema/discrepancies.js.map +1 -1
  21. package/dist/feature-libraries/modular-schema/index.d.ts +1 -1
  22. package/dist/feature-libraries/modular-schema/index.d.ts.map +1 -1
  23. package/dist/feature-libraries/modular-schema/index.js +2 -2
  24. package/dist/feature-libraries/modular-schema/index.js.map +1 -1
  25. package/dist/packageVersion.d.ts +1 -1
  26. package/dist/packageVersion.js +1 -1
  27. package/dist/packageVersion.js.map +1 -1
  28. package/dist/shared-tree/treeApi.js +4 -1
  29. package/dist/shared-tree/treeApi.js.map +1 -1
  30. package/dist/simple-tree/api/schemaCreationUtilities.d.ts +12 -14
  31. package/dist/simple-tree/api/schemaCreationUtilities.d.ts.map +1 -1
  32. package/dist/simple-tree/api/schemaCreationUtilities.js +9 -7
  33. package/dist/simple-tree/api/schemaCreationUtilities.js.map +1 -1
  34. package/dist/simple-tree/api/schemaFactory.d.ts +2 -0
  35. package/dist/simple-tree/api/schemaFactory.d.ts.map +1 -1
  36. package/dist/simple-tree/api/schemaFactory.js +4 -1
  37. package/dist/simple-tree/api/schemaFactory.js.map +1 -1
  38. package/dist/simple-tree/api/schemaFactoryRecursive.js.map +1 -1
  39. package/dist/simple-tree/core/treeNodeKernel.d.ts +4 -5
  40. package/dist/simple-tree/core/treeNodeKernel.d.ts.map +1 -1
  41. package/dist/simple-tree/core/treeNodeKernel.js +63 -67
  42. package/dist/simple-tree/core/treeNodeKernel.js.map +1 -1
  43. package/dist/simple-tree/objectNode.d.ts +1 -1
  44. package/dist/simple-tree/objectNode.js.map +1 -1
  45. package/dist/simple-tree/objectNodeTypes.d.ts +3 -0
  46. package/dist/simple-tree/objectNodeTypes.d.ts.map +1 -1
  47. package/dist/simple-tree/objectNodeTypes.js +3 -1
  48. package/dist/simple-tree/objectNodeTypes.js.map +1 -1
  49. package/docs/.attachments/object-merge-semantics.drawio +145 -0
  50. package/docs/user-facing/array-merge-semantics.md +344 -0
  51. package/docs/user-facing/map-merge-semantics.md +128 -0
  52. package/docs/user-facing/merge-semantics.md +7 -3
  53. package/docs/user-facing/object-merge-semantics.md +77 -0
  54. package/lib/feature-libraries/chunked-forest/basicChunk.d.ts +26 -5
  55. package/lib/feature-libraries/chunked-forest/basicChunk.d.ts.map +1 -1
  56. package/lib/feature-libraries/chunked-forest/basicChunk.js +15 -5
  57. package/lib/feature-libraries/chunked-forest/basicChunk.js.map +1 -1
  58. package/lib/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
  59. package/lib/feature-libraries/chunked-forest/chunkedForest.js +5 -0
  60. package/lib/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
  61. package/lib/feature-libraries/index.d.ts +1 -1
  62. package/lib/feature-libraries/index.d.ts.map +1 -1
  63. package/lib/feature-libraries/index.js +1 -1
  64. package/lib/feature-libraries/index.js.map +1 -1
  65. package/lib/feature-libraries/modular-schema/discrepancies.d.ts +27 -27
  66. package/lib/feature-libraries/modular-schema/discrepancies.d.ts.map +1 -1
  67. package/lib/feature-libraries/modular-schema/discrepancies.js +31 -31
  68. package/lib/feature-libraries/modular-schema/discrepancies.js.map +1 -1
  69. package/lib/feature-libraries/modular-schema/index.d.ts +1 -1
  70. package/lib/feature-libraries/modular-schema/index.d.ts.map +1 -1
  71. package/lib/feature-libraries/modular-schema/index.js +1 -1
  72. package/lib/feature-libraries/modular-schema/index.js.map +1 -1
  73. package/lib/packageVersion.d.ts +1 -1
  74. package/lib/packageVersion.js +1 -1
  75. package/lib/packageVersion.js.map +1 -1
  76. package/lib/shared-tree/treeApi.js +5 -2
  77. package/lib/shared-tree/treeApi.js.map +1 -1
  78. package/lib/simple-tree/api/schemaCreationUtilities.d.ts +12 -14
  79. package/lib/simple-tree/api/schemaCreationUtilities.d.ts.map +1 -1
  80. package/lib/simple-tree/api/schemaCreationUtilities.js +9 -7
  81. package/lib/simple-tree/api/schemaCreationUtilities.js.map +1 -1
  82. package/lib/simple-tree/api/schemaFactory.d.ts +2 -0
  83. package/lib/simple-tree/api/schemaFactory.d.ts.map +1 -1
  84. package/lib/simple-tree/api/schemaFactory.js +4 -1
  85. package/lib/simple-tree/api/schemaFactory.js.map +1 -1
  86. package/lib/simple-tree/api/schemaFactoryRecursive.js.map +1 -1
  87. package/lib/simple-tree/core/treeNodeKernel.d.ts +4 -5
  88. package/lib/simple-tree/core/treeNodeKernel.d.ts.map +1 -1
  89. package/lib/simple-tree/core/treeNodeKernel.js +64 -68
  90. package/lib/simple-tree/core/treeNodeKernel.js.map +1 -1
  91. package/lib/simple-tree/objectNode.d.ts +1 -1
  92. package/lib/simple-tree/objectNode.js.map +1 -1
  93. package/lib/simple-tree/objectNodeTypes.d.ts +3 -0
  94. package/lib/simple-tree/objectNodeTypes.d.ts.map +1 -1
  95. package/lib/simple-tree/objectNodeTypes.js +3 -1
  96. package/lib/simple-tree/objectNodeTypes.js.map +1 -1
  97. package/package.json +20 -20
  98. package/src/feature-libraries/chunked-forest/basicChunk.ts +12 -4
  99. package/src/feature-libraries/chunked-forest/chunkedForest.ts +5 -0
  100. package/src/feature-libraries/index.ts +1 -1
  101. package/src/feature-libraries/modular-schema/discrepancies.ts +75 -77
  102. package/src/feature-libraries/modular-schema/index.ts +4 -1
  103. package/src/packageVersion.ts +1 -1
  104. package/src/shared-tree/treeApi.ts +7 -5
  105. package/src/simple-tree/api/schemaCreationUtilities.ts +29 -17
  106. package/src/simple-tree/api/schemaFactory.ts +25 -18
  107. package/src/simple-tree/api/schemaFactoryRecursive.ts +1 -1
  108. package/src/simple-tree/core/treeNodeKernel.ts +62 -64
  109. package/src/simple-tree/objectNode.ts +1 -1
  110. package/src/simple-tree/objectNodeTypes.ts +3 -1
@@ -17,7 +17,6 @@ import {
17
17
  assertFlexTreeEntityNotFreed,
18
18
  ContextSlot,
19
19
  flexTreeSlot,
20
- isFlexTreeNode,
21
20
  isFreedSymbol,
22
21
  LazyEntity,
23
22
  TreeStatus,
@@ -71,10 +70,15 @@ export function tryGetTreeNodeSchema(value: unknown): undefined | TreeNodeSchema
71
70
  }
72
71
 
73
72
  /** The {@link HydrationState} of a {@link TreeNodeKernel} before the kernel is hydrated */
74
- type UnhydratedState = Off;
73
+ interface UnhydratedState {
74
+ off: Off;
75
+ innerNode: UnhydratedFlexTreeNode;
76
+ }
75
77
 
76
78
  /** The {@link HydrationState} of a {@link TreeNodeKernel} after the kernel is hydrated */
77
79
  interface HydratedState {
80
+ /** The flex node for this kernel (lazy - undefined if it has not yet been demanded) */
81
+ innerNode?: FlexTreeNode;
78
82
  /** The {@link AnchorNode} that this node is associated with. */
79
83
  anchorNode: AnchorNode;
80
84
  /** All {@link Off | event deregistration functions} that should be run when the kernel is disposed. */
@@ -86,14 +90,13 @@ type HydrationState = UnhydratedState | HydratedState;
86
90
 
87
91
  /** True if and only if the given {@link HydrationState} is post-hydration */
88
92
  function isHydrated(state: HydrationState): state is HydratedState {
89
- return typeof state === "object";
93
+ return (state as Partial<HydratedState>).anchorNode !== undefined;
90
94
  }
91
95
 
92
96
  /**
93
97
  * Contains state and an internal API for managing {@link TreeNode}s.
94
98
  * @remarks All {@link TreeNode}s have an associated kernel object.
95
99
  * The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states.
96
- * When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method.
97
100
  */
98
101
  export class TreeNodeKernel {
99
102
  private disposed = false;
@@ -110,7 +113,7 @@ export class TreeNodeKernel {
110
113
  */
111
114
  public generationNumber: number = 0;
112
115
 
113
- #hydrationState: HydrationState = () => {};
116
+ #hydrationState: HydrationState;
114
117
 
115
118
  /**
116
119
  * Events registered before hydration.
@@ -133,7 +136,7 @@ export class TreeNodeKernel {
133
136
  public constructor(
134
137
  public readonly node: TreeNode,
135
138
  public readonly schema: TreeNodeSchema,
136
- private innerNode: InnerNode,
139
+ innerNode: InnerNode,
137
140
  private readonly initialContext: Context,
138
141
  ) {
139
142
  assert(!treeNodeToKernel.has(node), 0xa1a /* only one kernel per node can be made */);
@@ -144,9 +147,9 @@ export class TreeNodeKernel {
144
147
  mapTreeNodeToProxy.set(innerNode, node);
145
148
  // Register for change events from the unhydrated flex node.
146
149
  // These will be fired if the unhydrated node is edited, and will also be forwarded later to the hydrated node.
147
- this.#hydrationState = innerNode.events.on(
148
- "childrenChangedAfterBatch",
149
- ({ changedFields }) => {
150
+ this.#hydrationState = {
151
+ innerNode,
152
+ off: innerNode.events.on("childrenChangedAfterBatch", ({ changedFields }) => {
150
153
  this.#unhydratedEvents.value.emit("childrenChangedAfterBatch", {
151
154
  changedFields,
152
155
  });
@@ -161,15 +164,16 @@ export class TreeNodeKernel {
161
164
  // This cast is safe because the parent (if it exists) of an unhydrated flex node is always another unhydrated flex node.
162
165
  n = n.parentField.parent.parent as UnhydratedFlexTreeNode | undefined;
163
166
  }
164
- },
165
- );
167
+ }),
168
+ };
166
169
  } else {
167
170
  // Hydrated case
171
+ const { anchorNode } = innerNode;
168
172
  assert(
169
- !innerNode.anchorNode.slots.has(proxySlot),
173
+ !anchorNode.slots.has(proxySlot),
170
174
  0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */,
171
175
  );
172
- this.hydrate(innerNode.anchorNode);
176
+ this.#hydrationState = this.createHydratedState(anchorNode);
173
177
  }
174
178
  }
175
179
 
@@ -191,27 +195,11 @@ export class TreeNodeKernel {
191
195
  * Happens at most once for any given node.
192
196
  * Cleans up mappings to {@link UnhydratedFlexTreeNode} - it is assumed that they are no longer needed once the proxy has an anchor node.
193
197
  */
194
- public hydrate(anchorNode: AnchorNode): void {
198
+ private hydrate(anchorNode: AnchorNode): void {
195
199
  assert(!this.disposed, 0xa2a /* cannot hydrate a disposed node */);
196
200
  assert(!isHydrated(this.#hydrationState), 0xa2b /* hydration should only happen once */);
197
-
198
- // If the this node is raw and thus has a MapTreeNode, forget it:
199
- if (this.innerNode instanceof UnhydratedFlexTreeNode) {
200
- mapTreeNodeToProxy.delete(this.innerNode);
201
- }
202
-
203
- // However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced.
204
- anchorNode.slots.set(proxySlot, this.node);
205
- this.#hydrationState = {
206
- anchorNode,
207
- offAnchorNode: new Set([
208
- anchorNode.events.on("afterDestroy", () => this.dispose()),
209
- // TODO: this should be triggered on change even for unhydrated nodes.
210
- anchorNode.events.on("childrenChanging", () => {
211
- this.generationNumber += 1;
212
- }),
213
- ]),
214
- };
201
+ mapTreeNodeToProxy.delete(this.#hydrationState.innerNode);
202
+ this.#hydrationState = this.createHydratedState(anchorNode);
215
203
 
216
204
  // If needed, register forwarding emitters for events from before hydration
217
205
  if (this.#unhydratedEvents.evaluated) {
@@ -228,6 +216,20 @@ export class TreeNodeKernel {
228
216
  }
229
217
  }
230
218
 
219
+ private createHydratedState(anchorNode: AnchorNode): HydratedState {
220
+ anchorNode.slots.set(proxySlot, this.node);
221
+ return {
222
+ anchorNode,
223
+ offAnchorNode: new Set([
224
+ anchorNode.events.on("afterDestroy", () => this.dispose()),
225
+ // TODO: this should be triggered on change even for unhydrated nodes.
226
+ anchorNode.events.on("childrenChanging", () => {
227
+ this.generationNumber += 1;
228
+ }),
229
+ ]),
230
+ };
231
+ }
232
+
231
233
  public getStatus(): TreeStatus {
232
234
  if (this.disposed) {
233
235
  return TreeStatus.Deleted;
@@ -282,13 +284,12 @@ export class TreeNodeKernel {
282
284
  * Note that for "marinated" nodes, this FlexTreeNode exists and returns it: it does not return the MapTreeNode which is the current InnerNode.
283
285
  */
284
286
  public getOrCreateInnerNode(allowFreed = false): InnerNode {
285
- if (!(this.innerNode instanceof UnhydratedFlexTreeNode)) {
286
- // Cooked case
287
- return this.innerNode;
287
+ if (!isHydrated(this.#hydrationState)) {
288
+ return this.#hydrationState.innerNode; // Unhydrated case
288
289
  }
289
290
 
290
- if (!isHydrated(this.#hydrationState)) {
291
- return this.innerNode;
291
+ if (this.#hydrationState.innerNode !== undefined) {
292
+ return this.#hydrationState.innerNode; // Cooked case
292
293
  }
293
294
 
294
295
  // Marinated case -> cooked
@@ -296,21 +297,23 @@ export class TreeNodeKernel {
296
297
  // The proxy is bound to an anchor node, but it may or may not have an actual flex node yet
297
298
  const flexNode = anchorNode.slots.get(flexTreeSlot);
298
299
  if (flexNode !== undefined) {
299
- this.innerNode = flexNode;
300
- return flexNode; // If it does have a flex node, return it...
301
- } // ...otherwise, the flex node must be created
302
- const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context");
303
- const cursor = context.checkout.forest.allocateCursor("getFlexNode");
304
- context.checkout.forest.moveCursorToPath(anchorNode, cursor);
305
- const newFlexNode = makeTree(context, cursor);
306
- cursor.free();
307
- this.innerNode = newFlexNode;
308
- // Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode
309
- anchorForgetters?.get(this.node)?.();
310
- if (!allowFreed) {
311
- assertFlexTreeEntityNotFreed(newFlexNode);
300
+ // If the flex node already exists, use it...
301
+ this.#hydrationState.innerNode = flexNode;
302
+ } else {
303
+ // ...otherwise, the flex node must be created
304
+ const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context");
305
+ const cursor = context.checkout.forest.allocateCursor("getFlexNode");
306
+ context.checkout.forest.moveCursorToPath(anchorNode, cursor);
307
+ this.#hydrationState.innerNode = makeTree(context, cursor);
308
+ cursor.free();
309
+ // Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode
310
+ anchorForgetters?.get(this.node)?.();
311
+ if (!allowFreed) {
312
+ assertFlexTreeEntityNotFreed(this.#hydrationState.innerNode);
313
+ }
312
314
  }
313
- return newFlexNode;
315
+
316
+ return this.#hydrationState.innerNode;
314
317
  }
315
318
 
316
319
  /**
@@ -344,24 +347,19 @@ export class TreeNodeKernel {
344
347
  /**
345
348
  * Retrieves the InnerNode associated with the given target via {@link setInnerNode}, if any.
346
349
  * @remarks
347
- * If `target` is a unhydrated node, returns its MapTreeNode.
350
+ * If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode.
348
351
  * If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode.
349
- * If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined.
352
+ * If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined.
350
353
  */
351
354
  public tryGetInnerNode(): InnerNode | undefined {
352
- if (isFlexTreeNode(this.innerNode)) {
353
- // Cooked case
354
- return this.innerNode;
355
- }
356
-
357
- if (!isHydrated(this.#hydrationState)) {
358
- return this.innerNode;
355
+ if (isHydrated(this.#hydrationState)) {
356
+ return (
357
+ this.#hydrationState.innerNode ??
358
+ this.#hydrationState.anchorNode.slots.get(flexTreeSlot)
359
+ );
359
360
  }
360
361
 
361
- // Marinated case -> cooked
362
- const anchorNode = this.#hydrationState.anchorNode;
363
- // The proxy is bound to an anchor node, but it may or may not have an actual flex node yet
364
- return anchorNode.slots.get(flexTreeSlot);
362
+ return this.#hydrationState.innerNode;
365
363
  }
366
364
  }
367
365
 
@@ -47,7 +47,7 @@ import { TreeNodeValid, type MostDerivedData } from "./treeNodeValid.js";
47
47
  import { getUnhydratedContext } from "./createContext.js";
48
48
 
49
49
  /**
50
- * Helper used to produce types for object nodes.
50
+ * Generates the properties for an ObjectNode from its field schema object.
51
51
  * @system @public
52
52
  */
53
53
  export type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFieldSchema>> = {
@@ -58,7 +58,9 @@ export interface ObjectNodeSchemaInternalData {
58
58
  }
59
59
 
60
60
  export const ObjectNodeSchema = {
61
- // instanceof-based narrowing support for Javascript and TypeScript 5.3 or newer.
61
+ /**
62
+ * instanceof-based narrowing support for ObjectNodeSchema in Javascript and TypeScript 5.3 or newer.
63
+ */
62
64
  [Symbol.hasInstance](value: TreeNodeSchema): value is ObjectNodeSchema {
63
65
  return isObjectNodeSchema(value);
64
66
  },