@fluidframework/tree 2.2.0 → 2.3.0-288113

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 (144) hide show
  1. package/api-report/tree.alpha.api.md +39 -3
  2. package/api-report/tree.beta.api.md +8 -3
  3. package/api-report/tree.public.api.md +8 -3
  4. package/dist/beta.d.ts +1 -0
  5. package/dist/core/tree/anchorSet.d.ts +4 -6
  6. package/dist/core/tree/anchorSet.d.ts.map +1 -1
  7. package/dist/core/tree/anchorSet.js +11 -1
  8. package/dist/core/tree/anchorSet.js.map +1 -1
  9. package/dist/events/events.d.ts +7 -1
  10. package/dist/events/events.d.ts.map +1 -1
  11. package/dist/events/events.js +5 -2
  12. package/dist/events/events.js.map +1 -1
  13. package/dist/feature-libraries/flex-map-tree/mapTreeNode.d.ts +0 -2
  14. package/dist/feature-libraries/flex-map-tree/mapTreeNode.d.ts.map +1 -1
  15. package/dist/feature-libraries/flex-map-tree/mapTreeNode.js +0 -12
  16. package/dist/feature-libraries/flex-map-tree/mapTreeNode.js.map +1 -1
  17. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +4 -95
  18. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  19. package/dist/feature-libraries/flex-tree/flexTreeTypes.js +1 -30
  20. package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  21. package/dist/feature-libraries/flex-tree/index.d.ts +2 -2
  22. package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
  23. package/dist/feature-libraries/flex-tree/index.js +1 -3
  24. package/dist/feature-libraries/flex-tree/index.js.map +1 -1
  25. package/dist/feature-libraries/flex-tree/lazyField.d.ts +0 -1
  26. package/dist/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
  27. package/dist/feature-libraries/flex-tree/lazyField.js +0 -3
  28. package/dist/feature-libraries/flex-tree/lazyField.js.map +1 -1
  29. package/dist/feature-libraries/flex-tree/lazyNode.d.ts +3 -10
  30. package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  31. package/dist/feature-libraries/flex-tree/lazyNode.js +2 -87
  32. package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  33. package/dist/feature-libraries/index.d.ts +1 -1
  34. package/dist/feature-libraries/index.d.ts.map +1 -1
  35. package/dist/feature-libraries/index.js +2 -4
  36. package/dist/feature-libraries/index.js.map +1 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +36 -31
  40. package/dist/index.js.map +1 -1
  41. package/dist/packageVersion.d.ts +1 -1
  42. package/dist/packageVersion.d.ts.map +1 -1
  43. package/dist/packageVersion.js +1 -1
  44. package/dist/packageVersion.js.map +1 -1
  45. package/dist/public.d.ts +1 -0
  46. package/dist/simple-tree/api/treeNodeApi.d.ts +1 -1
  47. package/dist/simple-tree/api/treeNodeApi.d.ts.map +1 -1
  48. package/dist/simple-tree/api/treeNodeApi.js +30 -2
  49. package/dist/simple-tree/api/treeNodeApi.js.map +1 -1
  50. package/dist/simple-tree/arrayNode.d.ts.map +1 -1
  51. package/dist/simple-tree/arrayNode.js +5 -19
  52. package/dist/simple-tree/arrayNode.js.map +1 -1
  53. package/dist/simple-tree/core/index.d.ts +1 -1
  54. package/dist/simple-tree/core/index.d.ts.map +1 -1
  55. package/dist/simple-tree/core/index.js.map +1 -1
  56. package/dist/simple-tree/core/treeNodeKernel.d.ts +18 -5
  57. package/dist/simple-tree/core/treeNodeKernel.d.ts.map +1 -1
  58. package/dist/simple-tree/core/treeNodeKernel.js +57 -21
  59. package/dist/simple-tree/core/treeNodeKernel.js.map +1 -1
  60. package/dist/simple-tree/core/types.d.ts +28 -10
  61. package/dist/simple-tree/core/types.d.ts.map +1 -1
  62. package/dist/simple-tree/core/types.js.map +1 -1
  63. package/dist/simple-tree/index.d.ts +1 -1
  64. package/dist/simple-tree/index.d.ts.map +1 -1
  65. package/dist/simple-tree/index.js.map +1 -1
  66. package/lib/beta.d.ts +1 -0
  67. package/lib/core/tree/anchorSet.d.ts +4 -6
  68. package/lib/core/tree/anchorSet.d.ts.map +1 -1
  69. package/lib/core/tree/anchorSet.js +11 -1
  70. package/lib/core/tree/anchorSet.js.map +1 -1
  71. package/lib/events/events.d.ts +7 -1
  72. package/lib/events/events.d.ts.map +1 -1
  73. package/lib/events/events.js +5 -2
  74. package/lib/events/events.js.map +1 -1
  75. package/lib/feature-libraries/flex-map-tree/mapTreeNode.d.ts +0 -2
  76. package/lib/feature-libraries/flex-map-tree/mapTreeNode.d.ts.map +1 -1
  77. package/lib/feature-libraries/flex-map-tree/mapTreeNode.js +0 -12
  78. package/lib/feature-libraries/flex-map-tree/mapTreeNode.js.map +1 -1
  79. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +4 -95
  80. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  81. package/lib/feature-libraries/flex-tree/flexTreeTypes.js +0 -29
  82. package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  83. package/lib/feature-libraries/flex-tree/index.d.ts +2 -2
  84. package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
  85. package/lib/feature-libraries/flex-tree/index.js +1 -1
  86. package/lib/feature-libraries/flex-tree/index.js.map +1 -1
  87. package/lib/feature-libraries/flex-tree/lazyField.d.ts +0 -1
  88. package/lib/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
  89. package/lib/feature-libraries/flex-tree/lazyField.js +0 -3
  90. package/lib/feature-libraries/flex-tree/lazyField.js.map +1 -1
  91. package/lib/feature-libraries/flex-tree/lazyNode.d.ts +3 -10
  92. package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  93. package/lib/feature-libraries/flex-tree/lazyNode.js +3 -86
  94. package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  95. package/lib/feature-libraries/index.d.ts +1 -1
  96. package/lib/feature-libraries/index.d.ts.map +1 -1
  97. package/lib/feature-libraries/index.js +1 -1
  98. package/lib/feature-libraries/index.js.map +1 -1
  99. package/lib/index.d.ts +2 -2
  100. package/lib/index.d.ts.map +1 -1
  101. package/lib/index.js +4 -0
  102. package/lib/index.js.map +1 -1
  103. package/lib/packageVersion.d.ts +1 -1
  104. package/lib/packageVersion.d.ts.map +1 -1
  105. package/lib/packageVersion.js +1 -1
  106. package/lib/packageVersion.js.map +1 -1
  107. package/lib/public.d.ts +1 -0
  108. package/lib/simple-tree/api/treeNodeApi.d.ts +1 -1
  109. package/lib/simple-tree/api/treeNodeApi.d.ts.map +1 -1
  110. package/lib/simple-tree/api/treeNodeApi.js +30 -2
  111. package/lib/simple-tree/api/treeNodeApi.js.map +1 -1
  112. package/lib/simple-tree/arrayNode.d.ts.map +1 -1
  113. package/lib/simple-tree/arrayNode.js +5 -19
  114. package/lib/simple-tree/arrayNode.js.map +1 -1
  115. package/lib/simple-tree/core/index.d.ts +1 -1
  116. package/lib/simple-tree/core/index.d.ts.map +1 -1
  117. package/lib/simple-tree/core/index.js.map +1 -1
  118. package/lib/simple-tree/core/treeNodeKernel.d.ts +18 -5
  119. package/lib/simple-tree/core/treeNodeKernel.d.ts.map +1 -1
  120. package/lib/simple-tree/core/treeNodeKernel.js +58 -22
  121. package/lib/simple-tree/core/treeNodeKernel.js.map +1 -1
  122. package/lib/simple-tree/core/types.d.ts +28 -10
  123. package/lib/simple-tree/core/types.d.ts.map +1 -1
  124. package/lib/simple-tree/core/types.js.map +1 -1
  125. package/lib/simple-tree/index.d.ts +1 -1
  126. package/lib/simple-tree/index.d.ts.map +1 -1
  127. package/lib/simple-tree/index.js.map +1 -1
  128. package/package.json +22 -32
  129. package/src/core/tree/anchorSet.ts +20 -9
  130. package/src/events/events.ts +10 -2
  131. package/src/feature-libraries/flex-map-tree/mapTreeNode.ts +0 -21
  132. package/src/feature-libraries/flex-tree/flexTreeTypes.ts +3 -170
  133. package/src/feature-libraries/flex-tree/index.ts +1 -17
  134. package/src/feature-libraries/flex-tree/lazyField.ts +0 -6
  135. package/src/feature-libraries/flex-tree/lazyNode.ts +3 -154
  136. package/src/feature-libraries/index.ts +0 -14
  137. package/src/index.ts +8 -0
  138. package/src/packageVersion.ts +1 -1
  139. package/src/simple-tree/api/treeNodeApi.ts +37 -5
  140. package/src/simple-tree/arrayNode.ts +3 -12
  141. package/src/simple-tree/core/index.ts +1 -0
  142. package/src/simple-tree/core/treeNodeKernel.ts +88 -29
  143. package/src/simple-tree/core/types.ts +35 -9
  144. package/src/simple-tree/index.ts +1 -0
@@ -4,9 +4,15 @@
4
4
  */
5
5
 
6
6
  import { assert } from "@fluidframework/core-utils/internal";
7
- import { createEmitter, type Listenable, type Off } from "../../events/index.js";
8
- import type { TreeChangeEvents, TreeNode, Unhydrated } from "./types.js";
9
- import type { AnchorNode } from "../../core/index.js";
7
+ import {
8
+ createEmitter,
9
+ type HasListeners,
10
+ type IEmitter,
11
+ type Listenable,
12
+ type Off,
13
+ } from "../../events/index.js";
14
+ import type { TreeNode, Unhydrated } from "./types.js";
15
+ import type { AnchorEvents, AnchorNode } from "../../core/index.js";
10
16
  import {
11
17
  flexTreeSlot,
12
18
  isFreedSymbol,
@@ -60,12 +66,48 @@ export function tryGetTreeNodeSchema(value: unknown): undefined | TreeNodeSchema
60
66
  * The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states.
61
67
  * When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method.
62
68
  */
63
- export class TreeNodeKernel implements Listenable<TreeChangeEvents> {
69
+ export class TreeNodeKernel implements Listenable<KernelEvents> {
70
+ private disposed = false;
71
+
72
+ /**
73
+ * Generation number which is incremented any time we have an edit on the node.
74
+ * Used during iteration to make sure there has been no edits that were concurrently made.
75
+ */
76
+ public generationNumber: number = 0;
77
+
64
78
  #hydrated?: {
65
79
  anchorNode: AnchorNode;
66
- offAnchorNode: Off;
80
+ offAnchorNode: Set<Off>;
67
81
  };
68
- #events = createEmitter<TreeChangeEvents>();
82
+
83
+ /**
84
+ * Events registered before hydration.
85
+ * @remarks
86
+ *
87
+ */
88
+ #preHydrationEvents?: Listenable<KernelEvents> &
89
+ IEmitter<KernelEvents> &
90
+ HasListeners<KernelEvents>;
91
+
92
+ /**
93
+ * Get the listener.
94
+ * @remarks
95
+ * If before hydration, allocates and uses `#preHydrationEvents`, otherwise the anchorNode.
96
+ * This design avoids allocating `#preHydrationEvents` if unneeded.
97
+ *
98
+ * This design also avoids extra forwarding overhead for events from anchorNode and also
99
+ * avoids registering for events that the are unneeded.
100
+ * This means optimizations like skipping processing data in subtrees where no subtreeChanged events are subscribed to would be able to work,
101
+ * since this code does not unconditionally subscribe to those events (like a design simply forwarding all events would).
102
+ */
103
+ get #events(): Listenable<KernelEvents> {
104
+ if (this.#hydrated === undefined) {
105
+ this.#preHydrationEvents ??= createEmitter<KernelEvents>();
106
+ return this.#preHydrationEvents;
107
+ } else {
108
+ return this.#hydrated.anchorNode;
109
+ }
110
+ }
69
111
 
70
112
  /**
71
113
  * Create a TreeNodeKernel which can be looked up with {@link getKernel}.
@@ -80,37 +122,50 @@ export class TreeNodeKernel implements Listenable<TreeChangeEvents> {
80
122
  treeNodeToKernel.set(node, this);
81
123
  }
82
124
 
125
+ /**
126
+ * Transition from {@link Unhydrated} to hydrated.
127
+ * @remarks
128
+ * Happens at most once for any given node.
129
+ */
83
130
  public hydrate(anchorNode: AnchorNode): void {
84
- const offChildrenChanged = anchorNode.on("childrenChangedAfterBatch", () => {
85
- this.#events.emit("nodeChanged");
86
- });
87
-
88
- const offSubtreeChanged = anchorNode.on("subtreeChangedAfterBatch", () => {
89
- this.#events.emit("treeChanged");
90
- });
91
-
92
- const offAfterDestroy = anchorNode.on("afterDestroy", () => this.dispose());
131
+ assert(!this.disposed, "cannot use a disposed node");
93
132
 
94
133
  this.#hydrated = {
95
134
  anchorNode,
96
- offAnchorNode: () => {
97
- offChildrenChanged();
98
- offSubtreeChanged();
99
- offAfterDestroy();
100
- },
135
+ offAnchorNode: new Set([
136
+ anchorNode.on("afterDestroy", () => this.dispose()),
137
+ // TODO: this should be triggered on change even for unhydrated nodes.
138
+ anchorNode.on("childrenChanging", () => {
139
+ this.generationNumber += 1;
140
+ }),
141
+ ]),
101
142
  };
102
- }
103
143
 
104
- public dehydrate(): void {
105
- this.#hydrated?.offAnchorNode?.();
106
- this.#hydrated = undefined;
144
+ // If needed, register forwarding emitters for events from before hydration
145
+ if (this.#preHydrationEvents !== undefined) {
146
+ for (const eventName of kernelEvents) {
147
+ if (this.#preHydrationEvents.hasListeners(eventName)) {
148
+ this.#hydrated.offAnchorNode.add(
149
+ // Argument is forwarded between matching events, so the type should be correct.
150
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
151
+ anchorNode.on(eventName, (arg: any) =>
152
+ this.#preHydrationEvents?.emit(eventName, arg),
153
+ ),
154
+ );
155
+ }
156
+ }
157
+ }
107
158
  }
108
159
 
109
160
  public isHydrated(): boolean {
161
+ assert(!this.disposed, "cannot use a disposed node");
110
162
  return this.#hydrated !== undefined;
111
163
  }
112
164
 
113
165
  public getStatus(): TreeStatus {
166
+ if (this.disposed) {
167
+ return TreeStatus.Deleted;
168
+ }
114
169
  if (this.#hydrated?.anchorNode === undefined) {
115
170
  return TreeStatus.New;
116
171
  }
@@ -127,15 +182,19 @@ export class TreeNodeKernel implements Listenable<TreeChangeEvents> {
127
182
  return treeStatusFromAnchorCache(this.#hydrated.anchorNode);
128
183
  }
129
184
 
130
- public on<K extends keyof TreeChangeEvents>(
131
- eventName: K,
132
- listener: TreeChangeEvents[K],
133
- ): Off {
185
+ public on<K extends keyof KernelEvents>(eventName: K, listener: KernelEvents[K]): Off {
134
186
  return this.#events.on(eventName, listener);
135
187
  }
136
188
 
137
189
  public dispose(): void {
138
- this.dehydrate();
190
+ this.disposed = true;
191
+ for (const off of this.#hydrated?.offAnchorNode ?? []) {
192
+ off();
193
+ }
139
194
  // TODO: go to the context and remove myself from withAnchors
140
195
  }
141
196
  }
197
+
198
+ const kernelEvents = ["childrenChangedAfterBatch", "subtreeChangedAfterBatch"] as const;
199
+
200
+ type KernelEvents = Pick<AnchorEvents, (typeof kernelEvents)[number]>;
@@ -24,9 +24,27 @@ import { isFlexTreeNode, type FlexTreeNode } from "../../feature-libraries/index
24
24
  */
25
25
  export type Unhydrated<T> = T;
26
26
 
27
+ /**
28
+ * Data included for {@link TreeChangeEvents.nodeChanged}.
29
+ * @public
30
+ */
31
+ export interface NodeChangedData {
32
+ /**
33
+ * When the node changed is an object or Map node, this lists all the properties which changed.
34
+ * @remarks
35
+ * This only includes changes to the node itself (which would trigger {@link TreeChangeEvents.nodeChanged}).
36
+ */
37
+ readonly changedProperties?: ReadonlySet<string>;
38
+ }
39
+
27
40
  /**
28
41
  * A collection of events that can be emitted by a {@link TreeNode}.
29
42
  *
43
+ * @remarks
44
+ * Currently events can be subscribed to for {@link Unhydrated} nodes, however no events will be triggered for the nodes until after they are hydrated.
45
+ * This is considered a known issue, and should be fixed in future versions.
46
+ * Do not rely on the fact that editing unhydrated nodes does not trigger their events.
47
+ *
30
48
  * @privateRemarks
31
49
  * TODO: add a way to subscribe to a specific field (for nodeChanged and treeChanged).
32
50
  * Probably have object node and map node specific APIs for this.
@@ -45,17 +63,15 @@ export type Unhydrated<T> = T;
45
63
  *
46
64
  * @sealed @public
47
65
  */
48
- export interface TreeChangeEvents {
66
+ export interface TreeChangeEvents<TNode = TreeNode> {
49
67
  /**
50
- * Emitted by a node after a batch of changes has been applied to the tree, if a change affected the node, where a
51
- * change is:
68
+ * Emitted by a node after a batch of changes has been applied to the tree, if any of the changes affected the node.
52
69
  *
53
- * - For an object node, when the value of one of its properties changes (i.e., the property's value is set
54
- * to something else, including `undefined`).
70
+ * - Object nodes define a change as the value of one of its properties changing (i.e., the property's value is set, including when set to undefined).
55
71
  *
56
- * - For an array node, when an element is added, removed, or moved.
72
+ * - Array node define a change as when an element is added, removed, moved or replaced.
57
73
  *
58
- * - For a map node, when an entry is added, updated, or removed.
74
+ * - Map nodes define a change as when an entry is added, updated, or removed.
59
75
  *
60
76
  * @remarks
61
77
  * This event is not emitted when:
@@ -70,15 +86,23 @@ export interface TreeChangeEvents {
70
86
  * For remote edits, this event is not guaranteed to occur in the same order or quantity that it did in
71
87
  * the client that made the original edit.
72
88
  *
73
- * When it is emitted, the tree is guaranteed to be in-schema.
89
+ * When the event is emitted, the tree is guaranteed to be in-schema.
74
90
  *
75
91
  * @privateRemarks
76
92
  * This event occurs whenever the apparent contents of the node instance change, regardless of what caused the change.
77
93
  * For example, it will fire when the local client reassigns a child, when part of a remote edit is applied to the
78
94
  * node, or when the node has to be updated due to resolution of a merge conflict
79
95
  * (for example a previously applied local change might be undone, then reapplied differently or not at all).
96
+ *
97
+ * TODO: defined and document event ordering (ex: bottom up, with nodeChanged before treeCHange on each level).
80
98
  */
81
- nodeChanged(): void;
99
+ nodeChanged(
100
+ data: NodeChangedData &
101
+ // For object and Map nodes, make properties specific to them required instead of optional:
102
+ (TNode extends WithType<string, NodeKind.Map | NodeKind.Object>
103
+ ? Required<Pick<NodeChangedData, "changedProperties">>
104
+ : unknown),
105
+ ): void;
82
106
 
83
107
  /**
84
108
  * Emitted by a node after a batch of changes has been applied to the tree, when something changed anywhere in the
@@ -99,6 +123,8 @@ export interface TreeChangeEvents {
99
123
  treeChanged(): void;
100
124
  }
101
125
 
126
+ export type IsListener2<TListener> = TListener extends (...args: any[]) => void ? true : false;
127
+
102
128
  /**
103
129
  * A non-{@link NodeKind.Leaf|leaf} SharedTree node. Includes objects, arrays, and maps.
104
130
  *
@@ -19,6 +19,7 @@ export {
19
19
  type Unhydrated,
20
20
  type InternalTreeNode,
21
21
  isTreeNode,
22
+ type NodeChangedData,
22
23
  } from "./core/index.js";
23
24
  export {
24
25
  type ITree,