@fluidframework/tree 2.62.0-356644 → 2.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/api-report/tree.alpha.api.md +23 -21
  3. package/api-report/tree.beta.api.md +15 -0
  4. package/api-report/tree.legacy.beta.api.md +18 -0
  5. package/dist/alpha.d.ts +8 -8
  6. package/dist/api.d.ts +17 -0
  7. package/dist/api.d.ts.map +1 -0
  8. package/dist/api.js +24 -0
  9. package/dist/api.js.map +1 -0
  10. package/dist/beta.d.ts +5 -0
  11. package/dist/codec/codec.d.ts +3 -5
  12. package/dist/codec/codec.d.ts.map +1 -1
  13. package/dist/codec/codec.js +9 -2
  14. package/dist/codec/codec.js.map +1 -1
  15. package/dist/codec/index.d.ts +0 -1
  16. package/dist/codec/index.d.ts.map +1 -1
  17. package/dist/codec/index.js +1 -3
  18. package/dist/codec/index.js.map +1 -1
  19. package/dist/core/rebase/utils.js +1 -1
  20. package/dist/core/rebase/utils.js.map +1 -1
  21. package/dist/core/tree/detachedFieldIndex.js +1 -1
  22. package/dist/core/tree/detachedFieldIndex.js.map +1 -1
  23. package/dist/external-utilities/index.d.ts +1 -1
  24. package/dist/external-utilities/index.d.ts.map +1 -1
  25. package/dist/external-utilities/index.js +1 -2
  26. package/dist/external-utilities/index.js.map +1 -1
  27. package/dist/external-utilities/typeboxValidator.d.ts +0 -13
  28. package/dist/external-utilities/typeboxValidator.d.ts.map +1 -1
  29. package/dist/external-utilities/typeboxValidator.js +3 -5
  30. package/dist/external-utilities/typeboxValidator.js.map +1 -1
  31. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +2 -0
  32. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  33. package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  34. package/dist/feature-libraries/flex-tree/index.d.ts +1 -0
  35. package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
  36. package/dist/feature-libraries/flex-tree/index.js +4 -1
  37. package/dist/feature-libraries/flex-tree/index.js.map +1 -1
  38. package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  39. package/dist/feature-libraries/flex-tree/lazyNode.js +15 -8
  40. package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  41. package/dist/feature-libraries/flex-tree/observer.d.ts +32 -0
  42. package/dist/feature-libraries/flex-tree/observer.d.ts.map +1 -0
  43. package/dist/feature-libraries/flex-tree/observer.js +33 -0
  44. package/dist/feature-libraries/flex-tree/observer.js.map +1 -0
  45. package/dist/feature-libraries/index.d.ts +1 -1
  46. package/dist/feature-libraries/index.d.ts.map +1 -1
  47. package/dist/feature-libraries/index.js +3 -1
  48. package/dist/feature-libraries/index.js.map +1 -1
  49. package/dist/index.d.ts +5 -5
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +9 -8
  52. package/dist/index.js.map +1 -1
  53. package/dist/legacy.d.ts +7 -1
  54. package/dist/packageVersion.d.ts +1 -1
  55. package/dist/packageVersion.d.ts.map +1 -1
  56. package/dist/packageVersion.js +1 -1
  57. package/dist/packageVersion.js.map +1 -1
  58. package/dist/shared-tree/index.d.ts +2 -2
  59. package/dist/shared-tree/index.d.ts.map +1 -1
  60. package/dist/shared-tree/index.js.map +1 -1
  61. package/dist/shared-tree/sharedTree.d.ts +9 -4
  62. package/dist/shared-tree/sharedTree.d.ts.map +1 -1
  63. package/dist/shared-tree/sharedTree.js +5 -4
  64. package/dist/shared-tree/sharedTree.js.map +1 -1
  65. package/dist/shared-tree/treeAlpha.d.ts +114 -1
  66. package/dist/shared-tree/treeAlpha.d.ts.map +1 -1
  67. package/dist/shared-tree/treeAlpha.js +140 -1
  68. package/dist/shared-tree/treeAlpha.js.map +1 -1
  69. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  70. package/dist/shared-tree/treeCheckout.js +2 -2
  71. package/dist/shared-tree/treeCheckout.js.map +1 -1
  72. package/dist/shared-tree-core/editManager.d.ts +9 -4
  73. package/dist/shared-tree-core/editManager.d.ts.map +1 -1
  74. package/dist/shared-tree-core/editManager.js +41 -28
  75. package/dist/shared-tree-core/editManager.js.map +1 -1
  76. package/dist/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -1
  77. package/dist/shared-tree-core/editManagerCodecsCommons.js +3 -3
  78. package/dist/shared-tree-core/editManagerCodecsCommons.js.map +1 -1
  79. package/dist/shared-tree-core/editManagerCodecsV5.d.ts.map +1 -1
  80. package/dist/shared-tree-core/editManagerCodecsV5.js +3 -3
  81. package/dist/shared-tree-core/editManagerCodecsV5.js.map +1 -1
  82. package/dist/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -1
  83. package/dist/shared-tree-core/messageCodecV1ToV4.js +2 -2
  84. package/dist/shared-tree-core/messageCodecV1ToV4.js.map +1 -1
  85. package/dist/shared-tree-core/messageCodecV5.d.ts.map +1 -1
  86. package/dist/shared-tree-core/messageCodecV5.js +1 -1
  87. package/dist/shared-tree-core/messageCodecV5.js.map +1 -1
  88. package/dist/shared-tree-core/sharedTreeCore.d.ts +1 -0
  89. package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  90. package/dist/shared-tree-core/sharedTreeCore.js +15 -10
  91. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  92. package/dist/simple-tree/api/tree.d.ts +7 -0
  93. package/dist/simple-tree/api/tree.d.ts.map +1 -1
  94. package/dist/simple-tree/api/tree.js +2 -0
  95. package/dist/simple-tree/api/tree.js.map +1 -1
  96. package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  97. package/dist/simple-tree/core/unhydratedFlexTree.js +7 -1
  98. package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  99. package/dist/treeFactory.d.ts +38 -9
  100. package/dist/treeFactory.d.ts.map +1 -1
  101. package/dist/treeFactory.js +44 -9
  102. package/dist/treeFactory.js.map +1 -1
  103. package/lib/alpha.d.ts +8 -8
  104. package/lib/api.d.ts +17 -0
  105. package/lib/api.d.ts.map +1 -0
  106. package/lib/api.js +22 -0
  107. package/lib/api.js.map +1 -0
  108. package/lib/beta.d.ts +5 -0
  109. package/lib/codec/codec.d.ts +3 -5
  110. package/lib/codec/codec.d.ts.map +1 -1
  111. package/lib/codec/codec.js +8 -1
  112. package/lib/codec/codec.js.map +1 -1
  113. package/lib/codec/index.d.ts +0 -1
  114. package/lib/codec/index.d.ts.map +1 -1
  115. package/lib/codec/index.js +0 -1
  116. package/lib/codec/index.js.map +1 -1
  117. package/lib/core/rebase/utils.js +1 -1
  118. package/lib/core/rebase/utils.js.map +1 -1
  119. package/lib/core/tree/detachedFieldIndex.js +2 -2
  120. package/lib/core/tree/detachedFieldIndex.js.map +1 -1
  121. package/lib/external-utilities/index.d.ts +1 -1
  122. package/lib/external-utilities/index.d.ts.map +1 -1
  123. package/lib/external-utilities/index.js +1 -1
  124. package/lib/external-utilities/index.js.map +1 -1
  125. package/lib/external-utilities/typeboxValidator.d.ts +0 -13
  126. package/lib/external-utilities/typeboxValidator.d.ts.map +1 -1
  127. package/lib/external-utilities/typeboxValidator.js +1 -3
  128. package/lib/external-utilities/typeboxValidator.js.map +1 -1
  129. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +2 -0
  130. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  131. package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  132. package/lib/feature-libraries/flex-tree/index.d.ts +1 -0
  133. package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
  134. package/lib/feature-libraries/flex-tree/index.js +1 -0
  135. package/lib/feature-libraries/flex-tree/index.js.map +1 -1
  136. package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  137. package/lib/feature-libraries/flex-tree/lazyNode.js +15 -8
  138. package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  139. package/lib/feature-libraries/flex-tree/observer.d.ts +32 -0
  140. package/lib/feature-libraries/flex-tree/observer.d.ts.map +1 -0
  141. package/lib/feature-libraries/flex-tree/observer.js +40 -0
  142. package/lib/feature-libraries/flex-tree/observer.js.map +1 -0
  143. package/lib/feature-libraries/index.d.ts +1 -1
  144. package/lib/feature-libraries/index.d.ts.map +1 -1
  145. package/lib/feature-libraries/index.js +1 -1
  146. package/lib/feature-libraries/index.js.map +1 -1
  147. package/lib/index.d.ts +5 -5
  148. package/lib/index.d.ts.map +1 -1
  149. package/lib/index.js +3 -3
  150. package/lib/index.js.map +1 -1
  151. package/lib/legacy.d.ts +7 -1
  152. package/lib/packageVersion.d.ts +1 -1
  153. package/lib/packageVersion.d.ts.map +1 -1
  154. package/lib/packageVersion.js +1 -1
  155. package/lib/packageVersion.js.map +1 -1
  156. package/lib/shared-tree/index.d.ts +2 -2
  157. package/lib/shared-tree/index.d.ts.map +1 -1
  158. package/lib/shared-tree/index.js.map +1 -1
  159. package/lib/shared-tree/sharedTree.d.ts +9 -4
  160. package/lib/shared-tree/sharedTree.d.ts.map +1 -1
  161. package/lib/shared-tree/sharedTree.js +6 -5
  162. package/lib/shared-tree/sharedTree.js.map +1 -1
  163. package/lib/shared-tree/treeAlpha.d.ts +114 -1
  164. package/lib/shared-tree/treeAlpha.d.ts.map +1 -1
  165. package/lib/shared-tree/treeAlpha.js +143 -4
  166. package/lib/shared-tree/treeAlpha.js.map +1 -1
  167. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  168. package/lib/shared-tree/treeCheckout.js +3 -3
  169. package/lib/shared-tree/treeCheckout.js.map +1 -1
  170. package/lib/shared-tree-core/editManager.d.ts +9 -4
  171. package/lib/shared-tree-core/editManager.d.ts.map +1 -1
  172. package/lib/shared-tree-core/editManager.js +41 -28
  173. package/lib/shared-tree-core/editManager.js.map +1 -1
  174. package/lib/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -1
  175. package/lib/shared-tree-core/editManagerCodecsCommons.js +3 -3
  176. package/lib/shared-tree-core/editManagerCodecsCommons.js.map +1 -1
  177. package/lib/shared-tree-core/editManagerCodecsV5.d.ts.map +1 -1
  178. package/lib/shared-tree-core/editManagerCodecsV5.js +3 -3
  179. package/lib/shared-tree-core/editManagerCodecsV5.js.map +1 -1
  180. package/lib/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -1
  181. package/lib/shared-tree-core/messageCodecV1ToV4.js +2 -2
  182. package/lib/shared-tree-core/messageCodecV1ToV4.js.map +1 -1
  183. package/lib/shared-tree-core/messageCodecV5.d.ts.map +1 -1
  184. package/lib/shared-tree-core/messageCodecV5.js +1 -1
  185. package/lib/shared-tree-core/messageCodecV5.js.map +1 -1
  186. package/lib/shared-tree-core/sharedTreeCore.d.ts +1 -0
  187. package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  188. package/lib/shared-tree-core/sharedTreeCore.js +15 -10
  189. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  190. package/lib/simple-tree/api/tree.d.ts +7 -0
  191. package/lib/simple-tree/api/tree.d.ts.map +1 -1
  192. package/lib/simple-tree/api/tree.js +2 -0
  193. package/lib/simple-tree/api/tree.js.map +1 -1
  194. package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  195. package/lib/simple-tree/core/unhydratedFlexTree.js +8 -2
  196. package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  197. package/lib/treeFactory.d.ts +38 -9
  198. package/lib/treeFactory.d.ts.map +1 -1
  199. package/lib/treeFactory.js +41 -8
  200. package/lib/treeFactory.js.map +1 -1
  201. package/package.json +25 -25
  202. package/src/api.ts +30 -0
  203. package/src/codec/codec.ts +12 -6
  204. package/src/codec/index.ts +0 -1
  205. package/src/core/rebase/utils.ts +1 -1
  206. package/src/core/tree/detachedFieldIndex.ts +2 -2
  207. package/src/external-utilities/index.ts +1 -1
  208. package/src/external-utilities/typeboxValidator.ts +1 -3
  209. package/src/feature-libraries/flex-tree/flexTreeTypes.ts +2 -0
  210. package/src/feature-libraries/flex-tree/index.ts +2 -0
  211. package/src/feature-libraries/flex-tree/lazyNode.ts +13 -3
  212. package/src/feature-libraries/flex-tree/observer.ts +64 -0
  213. package/src/feature-libraries/index.ts +3 -0
  214. package/src/index.ts +6 -4
  215. package/src/packageVersion.ts +1 -1
  216. package/src/shared-tree/index.ts +2 -0
  217. package/src/shared-tree/sharedTree.ts +13 -6
  218. package/src/shared-tree/treeAlpha.ts +309 -4
  219. package/src/shared-tree/treeCheckout.ts +6 -3
  220. package/src/shared-tree-core/editManager.ts +57 -30
  221. package/src/shared-tree-core/editManagerCodecsCommons.ts +12 -3
  222. package/src/shared-tree-core/editManagerCodecsV5.ts +9 -3
  223. package/src/shared-tree-core/messageCodecV1ToV4.ts +5 -2
  224. package/src/shared-tree-core/messageCodecV5.ts +4 -1
  225. package/src/shared-tree-core/sharedTreeCore.ts +20 -10
  226. package/src/simple-tree/api/tree.ts +8 -0
  227. package/src/simple-tree/core/unhydratedFlexTree.ts +11 -2
  228. package/src/treeFactory.ts +48 -8
  229. package/dist/codec/noopValidator.d.ts +0 -13
  230. package/dist/codec/noopValidator.d.ts.map +0 -1
  231. package/dist/codec/noopValidator.js +0 -17
  232. package/dist/codec/noopValidator.js.map +0 -1
  233. package/docs/user-facing/schema-evolution.md +0 -309
  234. package/lib/codec/noopValidator.d.ts +0 -13
  235. package/lib/codec/noopValidator.d.ts.map +0 -1
  236. package/lib/codec/noopValidator.js +0 -14
  237. package/lib/codec/noopValidator.js.map +0 -1
  238. package/src/codec/noopValidator.ts +0 -18
@@ -33,6 +33,7 @@ import {
33
33
  } from "./flexTreeTypes.js";
34
34
  import { LazyEntity } from "./lazyEntity.js";
35
35
  import { makeField } from "./lazyField.js";
36
+ import { currentObserver } from "./observer.js";
36
37
 
37
38
  /**
38
39
  * Get or create a {@link HydratedFlexTreeNode} for the given context at node indicated by the cursor.
@@ -118,18 +119,23 @@ export class LazyTreeNode extends LazyEntity<Anchor> implements HydratedFlexTree
118
119
  public readonly fields: Pick<Map<FieldKey, FlexTreeField>, typeof Symbol.iterator | "get"> =
119
120
  {
120
121
  get: (key: FieldKey): FlexTreeField | undefined => this.tryGetField(key),
121
- [Symbol.iterator]: (): IterableIterator<[FieldKey, FlexTreeField]> =>
122
- mapCursorFields(this.cursor, (cursor) => {
122
+ [Symbol.iterator]: (): IterableIterator<[FieldKey, FlexTreeField]> => {
123
+ currentObserver?.observeNodeFields(this);
124
+
125
+ return mapCursorFields(this.cursor, (cursor) => {
123
126
  const key: FieldKey = cursor.getFieldKey();
124
127
  const pair: [FieldKey, FlexTreeField] = [
125
128
  key,
126
129
  makeField(this.context, this.storedSchema.getFieldSchema(key).kind, cursor),
127
130
  ];
128
131
  return pair;
129
- }).values(),
132
+ }).values();
133
+ },
130
134
  };
131
135
 
132
136
  public tryGetField(fieldKey: FieldKey): FlexTreeField | undefined {
137
+ currentObserver?.observeNodeField(this, fieldKey);
138
+
133
139
  const schema = this.storedSchema.getFieldSchema(fieldKey);
134
140
  return inCursorField(this.cursor, fieldKey, (cursor) => {
135
141
  if (cursor.getFieldLength() === 0) {
@@ -140,6 +146,8 @@ export class LazyTreeNode extends LazyEntity<Anchor> implements HydratedFlexTree
140
146
  }
141
147
 
142
148
  public getBoxed(key: FieldKey): FlexTreeField {
149
+ currentObserver?.observeNodeField(this, key);
150
+
143
151
  const fieldSchema = this.storedSchema.getFieldSchema(key);
144
152
  return inCursorField(this.cursor, key, (cursor) => {
145
153
  return makeField(this.context, fieldSchema.kind, cursor);
@@ -157,6 +165,8 @@ export class LazyTreeNode extends LazyEntity<Anchor> implements HydratedFlexTree
157
165
  }
158
166
 
159
167
  public get parentField(): { readonly parent: FlexTreeField; readonly index: number } {
168
+ currentObserver?.observeParentOf(this);
169
+
160
170
  const cursor = this.cursor;
161
171
  const index = this.anchorNode.parentIndex;
162
172
  assert(cursor.fieldIndex === index, 0x786 /* mismatched indexes */);
@@ -0,0 +1,64 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { debugAssert } from "@fluidframework/core-utils/internal";
7
+ import type { FlexTreeNode } from "./flexTreeTypes.js";
8
+ import type { FieldKey } from "../../core/index.js";
9
+
10
+ /*
11
+ * This file sets up a static observation tracking system.
12
+ *
13
+ * This library used to contain a more general variant of this which was deleted in https://github.com/microsoft/FluidFramework/pull/18659.
14
+ * This pattern somewhat resembles the approach in https://github.com/tc39/proposal-signals.
15
+ */
16
+
17
+ /**
18
+ * An object informed about observation made to trees.
19
+ * @remarks
20
+ * See {@link withObservation} and {@link currentObserver}.
21
+ */
22
+ export interface Observer {
23
+ observeNodeFields(node: FlexTreeNode): void;
24
+ observeNodeField(node: FlexTreeNode, key: FieldKey): void;
25
+ observeParentOf(node: FlexTreeNode): void;
26
+ }
27
+
28
+ /**
29
+ * The current observer, if any.
30
+ * @remarks
31
+ * Set via {@link setObserver} as used by {@link withObservation}.
32
+ * It should not be assigned in any other way.
33
+ * @privateRemarks
34
+ * This is exported directly as a property instead of via a getter for reduced overhead (less code, faster access) as this is used on some hot paths and its performance matters.
35
+ * The case where this is undefined (no observation) is particularly important for performance as we do not want to regress code which is not using this feature very much.
36
+ * Since it is not exported outside the package, this seems like a fine tradeoff, but could be reevaluated with some benchmarking if needed.
37
+ */
38
+ export let currentObserver: Observer | undefined;
39
+
40
+ const observerStack: (Observer | undefined)[] = [];
41
+
42
+ function setObserver(newObserver: Observer | undefined): void {
43
+ observerStack.push(newObserver);
44
+ currentObserver = newObserver;
45
+ }
46
+
47
+ function clearObserver(): void {
48
+ debugAssert(() => observerStack.length > 0 || "Empty Observer stack on clear");
49
+ const popped = observerStack.pop();
50
+ debugAssert(() => popped === currentObserver || "Mismatched observer stack");
51
+ currentObserver = observerStack[observerStack.length - 1];
52
+ }
53
+
54
+ /**
55
+ * For the duration of `f`, pushes `newObserver` onto the observer stack, making it the {@link currentObserver}.
56
+ */
57
+ export function withObservation<T>(newObserver: Observer | undefined, f: () => T): T {
58
+ setObserver(newObserver);
59
+ try {
60
+ return f();
61
+ } finally {
62
+ clearObserver();
63
+ }
64
+ }
@@ -171,6 +171,9 @@ export {
171
171
  type FlexTreeHydratedContextMinimal,
172
172
  type HydratedFlexTreeNode,
173
173
  getOrCreateHydratedFlexTreeNode,
174
+ currentObserver,
175
+ withObservation,
176
+ type Observer,
174
177
  } from "./flex-tree/index.js";
175
178
 
176
179
  export {
package/src/index.ts CHANGED
@@ -57,6 +57,7 @@ export {
57
57
  export {
58
58
  type ITreeInternal,
59
59
  type SharedTreeOptions,
60
+ type SharedTreeOptionsBeta,
60
61
  type ForestType,
61
62
  type SharedTreeFormatOptions,
62
63
  SharedTreeFormatVersion,
@@ -69,6 +70,7 @@ export {
69
70
  independentInitializedView,
70
71
  type ViewContent,
71
72
  TreeAlpha,
73
+ type ObservationResults,
72
74
  type TreeIdentifierUtils,
73
75
  independentView,
74
76
  ForestTypeOptimized,
@@ -275,6 +277,8 @@ export {
275
277
  export {
276
278
  SharedTree,
277
279
  configuredSharedTree,
280
+ configuredSharedTreeBeta,
281
+ configuredSharedTreeBetaLegacy,
278
282
  } from "./treeFactory.js";
279
283
  export { SharedTreeAttributes, SharedTreeFactoryType } from "./sharedTreeAttributes.js";
280
284
  export { persistedToSimpleSchema } from "./shared-tree/index.js";
@@ -282,14 +286,11 @@ export { persistedToSimpleSchema } from "./shared-tree/index.js";
282
286
  export {
283
287
  type ICodecOptions,
284
288
  type CodecWriteOptions,
285
- type JsonValidator,
286
- type SchemaValidationFunction,
287
289
  FluidClientVersion,
288
290
  type FormatValidator,
289
291
  FormatValidatorNoOp,
290
292
  } from "./codec/index.js";
291
- export { noopValidator } from "./codec/index.js";
292
- export { typeboxValidator, FormatValidatorBasic } from "./external-utilities/index.js";
293
+ export { FormatValidatorBasic } from "./external-utilities/index.js";
293
294
 
294
295
  export type {
295
296
  // Type Testing
@@ -332,3 +333,4 @@ export type { MapNodeInsertableData } from "./simple-tree/index.js";
332
333
  export { JsonAsTree } from "./jsonDomainSchema.js";
333
334
  export { FluidSerializableAsTree } from "./serializableDomainSchema.js";
334
335
  export { TableSchema, type System_TableSchema } from "./tableSchema.js";
336
+ export { asAlpha } from "./api.js";
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/tree";
9
- export const pkgVersion = "2.62.0-356644";
9
+ export const pkgVersion = "2.62.0";
@@ -7,6 +7,7 @@ export {
7
7
  type ITreePrivate,
8
8
  type SharedTreeOptionsInternal,
9
9
  type SharedTreeOptions,
10
+ type SharedTreeOptionsBeta,
10
11
  SharedTreeKernel,
11
12
  getBranch,
12
13
  type ForestType,
@@ -49,6 +50,7 @@ export type { RunTransaction } from "./tree.js";
49
50
  export {
50
51
  TreeAlpha,
51
52
  type TreeIdentifierUtils,
53
+ type ObservationResults,
52
54
  } from "./treeAlpha.js";
53
55
 
54
56
  export {
@@ -20,8 +20,8 @@ import {
20
20
  import {
21
21
  type CodecWriteOptions,
22
22
  FluidClientVersion,
23
+ FormatValidatorNoOp,
23
24
  type ICodecOptions,
24
- noopValidator,
25
25
  } from "../codec/index.js";
26
26
  import {
27
27
  type FieldKey,
@@ -350,6 +350,7 @@ export class SharedTreeKernel
350
350
  viewWith: this.viewWith.bind(this),
351
351
  viewSharedBranchWith: this.viewBranchWith.bind(this),
352
352
  createSharedBranch: this.createSharedBranch.bind(this),
353
+ getSharedBranchIds: this.getSharedBranchIds.bind(this),
353
354
  kernel: this,
354
355
  };
355
356
  }
@@ -634,13 +635,19 @@ export const SharedTreeFormatVersion = {
634
635
  */
635
636
  export type SharedTreeFormatVersion = typeof SharedTreeFormatVersion;
636
637
 
638
+ /**
639
+ * Configuration options for SharedTree.
640
+ * @beta @input
641
+ */
642
+ export type SharedTreeOptionsBeta = ForestOptions;
643
+
637
644
  /**
638
645
  * Configuration options for SharedTree.
639
646
  * @alpha @input
640
647
  */
641
648
  export type SharedTreeOptions = Partial<CodecWriteOptions> &
642
649
  Partial<SharedTreeFormatOptions> &
643
- ForestOptions;
650
+ SharedTreeOptionsBeta;
644
651
 
645
652
  export interface SharedTreeOptionsInternal
646
653
  extends Omit<SharedTreeOptions, "treeEncodeType">,
@@ -718,7 +725,7 @@ export interface ForestType extends ErasedType<"ForestType"> {}
718
725
  * A simple implementation with minimal complexity and moderate debuggability, validation and performance.
719
726
  * @privateRemarks
720
727
  * The "ObjectForest" forest type.
721
- * @alpha
728
+ * @beta
722
729
  */
723
730
  export const ForestTypeReference = toForestType(
724
731
  (breaker: Breakable, schema: TreeStoredSchemaSubscription, idCompressor: IIdCompressor) =>
@@ -732,7 +739,7 @@ export const ForestTypeReference = toForestType(
732
739
  * Uses an internal representation optimized for size designed to scale to larger datasets with reduced overhead.
733
740
  * @privateRemarks
734
741
  * The "ChunkedForest" forest type.
735
- * @alpha
742
+ * @beta
736
743
  */
737
744
  export const ForestTypeOptimized = toForestType(
738
745
  (breaker: Breakable, schema: TreeStoredSchemaSubscription, idCompressor: IIdCompressor) =>
@@ -746,7 +753,7 @@ export const ForestTypeOptimized = toForestType(
746
753
  * May be asymptotically slower than {@link ForestTypeReference}, and may perform very badly with larger data sizes.
747
754
  * @privateRemarks
748
755
  * The "ObjectForest" forest type with expensive asserts for debugging.
749
- * @alpha
756
+ * @beta
750
757
  */
751
758
  export const ForestTypeExpensiveDebug = toForestType(
752
759
  (breaker: Breakable, schema: TreeStoredSchemaSubscription) =>
@@ -776,7 +783,7 @@ export function buildConfiguredForest(
776
783
  }
777
784
 
778
785
  export const defaultSharedTreeOptions: Required<SharedTreeOptionsInternal> = {
779
- jsonValidator: noopValidator,
786
+ jsonValidator: FormatValidatorNoOp,
780
787
  oldestCompatibleClient: FluidClientVersion.v2_0,
781
788
  forest: ForestTypeReference,
782
789
  treeEncodeType: TreeCompressionStrategy.Compressed,
@@ -17,7 +17,7 @@ import type { IIdCompressor } from "@fluidframework/id-compressor";
17
17
  import {
18
18
  asIndex,
19
19
  getKernel,
20
- type TreeNode,
20
+ TreeNode,
21
21
  type Unhydrated,
22
22
  TreeBeta,
23
23
  tryGetSchema,
@@ -55,15 +55,16 @@ import {
55
55
  convertField,
56
56
  toUnhydratedSchema,
57
57
  type TreeParsingOptions,
58
+ type NodeChangedData,
58
59
  } from "../simple-tree/index.js";
59
60
  import { brand, extractFromOpaque, type JsonCompatible } from "../util/index.js";
60
61
  import {
61
62
  FluidClientVersion,
62
- noopValidator,
63
63
  type ICodecOptions,
64
64
  type CodecWriteOptions,
65
+ FormatValidatorNoOp,
65
66
  } from "../codec/index.js";
66
- import { EmptyKey, type ITreeCursorSynchronous } from "../core/index.js";
67
+ import { EmptyKey, type FieldKey, type ITreeCursorSynchronous } from "../core/index.js";
67
68
  import {
68
69
  cursorForMapTreeField,
69
70
  defaultSchemaPolicy,
@@ -76,6 +77,9 @@ import {
76
77
  fluidVersionToFieldBatchCodecWriteVersion,
77
78
  type LocalNodeIdentifier,
78
79
  type FlexTreeSequenceField,
80
+ type FlexTreeNode,
81
+ type Observer,
82
+ withObservation,
79
83
  } from "../feature-libraries/index.js";
80
84
  import { independentInitializedView, type ViewContent } from "./independentView.js";
81
85
  import { SchematizingSimpleTreeView, ViewSlot } from "./schematizingTreeView.js";
@@ -419,6 +423,283 @@ export interface TreeAlpha {
419
423
  children(
420
424
  node: TreeNode,
421
425
  ): Iterable<[propertyKey: string | number, child: TreeNode | TreeLeafValue]>;
426
+
427
+ /**
428
+ * Track observations of any TreeNode content.
429
+ * @remarks
430
+ * This subscribes to changes to any nodes content observed during `trackDuring`.
431
+ *
432
+ * Currently this does not support tracking parentage (see {@link (TreeAlpha:interface).trackObservationsOnce} for a version which does):
433
+ * if accessing parentage during `trackDuring`, this will throw a usage error.
434
+ *
435
+ * This also does not track node status changes (e.g. whether a node is attached to a view or not).
436
+ * The current behavior of checking status is unspecified: future versions may track it, error, or ignore it.
437
+ *
438
+ * These subscriptions remain active until `unsubscribe` is called: `onInvalidation` may be called multiple times.
439
+ * See {@link (TreeAlpha:interface).trackObservationsOnce} for a version which automatically unsubscribes on the first invalidation.
440
+ * @privateRemarks
441
+ * This version, while more general than {@link (TreeAlpha:interface).trackObservationsOnce}, might be unnecessary.
442
+ * Maybe this should be removed and only `trackObservationsOnce` kept.
443
+ * Reevaluate this before stabilizing.
444
+ */
445
+ trackObservations<TResult>(
446
+ onInvalidation: () => void,
447
+ trackDuring: () => TResult,
448
+ ): ObservationResults<TResult>;
449
+
450
+ /**
451
+ * {@link (TreeAlpha:interface).trackObservations} except automatically unsubscribes when the first invalidation occurs.
452
+ * @remarks
453
+ * This also supports tracking parentage, unlike {@link (TreeAlpha:interface).trackObservations}, as long as the parent is not undefined.
454
+ *
455
+ * @example Simple cached value invalidation
456
+ * ```typescript
457
+ * // Compute and cache this "foo" value, and clear the cache when the fields read in the callback to compute it change.
458
+ * cachedFoo ??= TreeAlpha.trackObservationsOnce(
459
+ * () => {
460
+ * cachedFoo = undefined;
461
+ * },
462
+ * () => nodeA.someChild.bar + nodeB.someChild.baz,
463
+ * ).result;
464
+ * ```
465
+ *
466
+ * That is equivalent to doing the following:
467
+ * ```typescript
468
+ * if (cachedFoo === undefined) {
469
+ * cachedFoo = nodeA.someChild.bar + nodeB.someChild.baz;
470
+ * const invalidate = (): void => {
471
+ * cachedFoo = undefined;
472
+ * for (const u of unsubscribe) {
473
+ * u();
474
+ * }
475
+ * };
476
+ * const unsubscribe: (() => void)[] = [
477
+ * TreeBeta.on(nodeA, "nodeChanged", (data) => {
478
+ * if (data.changedProperties.has("someChild")) {
479
+ * invalidate();
480
+ * }
481
+ * }),
482
+ * TreeBeta.on(nodeB, "nodeChanged", (data) => {
483
+ * if (data.changedProperties.has("someChild")) {
484
+ * invalidate();
485
+ * }
486
+ * }),
487
+ * TreeBeta.on(nodeA.someChild, "nodeChanged", (data) => {
488
+ * if (data.changedProperties.has("bar")) {
489
+ * invalidate();
490
+ * }
491
+ * }),
492
+ * TreeBeta.on(nodeB.someChild, "nodeChanged", (data) => {
493
+ * if (data.changedProperties.has("baz")) {
494
+ * invalidate();
495
+ * }
496
+ * }),
497
+ * ];
498
+ * }
499
+ * ```
500
+ * @example Cached derived schema property
501
+ * ```typescript
502
+ * const factory = new SchemaFactory("com.example");
503
+ * class Vector extends factory.object("Vector", {
504
+ * x: SchemaFactory.number,
505
+ * y: SchemaFactory.number,
506
+ * }) {
507
+ * #length: number | undefined = undefined;
508
+ * public length(): number {
509
+ * if (this.#length === undefined) {
510
+ * const result = TreeAlpha.trackObservationsOnce(
511
+ * () => {
512
+ * this.#length = undefined;
513
+ * },
514
+ * () => Math.hypot(this.x, this.y),
515
+ * );
516
+ * this.#length = result.result;
517
+ * }
518
+ * return this.#length;
519
+ * }
520
+ * }
521
+ * const vec = new Vector({ x: 3, y: 4 });
522
+ * assert.equal(vec.length(), 5);
523
+ * vec.x = 0;
524
+ * assert.equal(vec.length(), 4);
525
+ * ```
526
+ */
527
+ trackObservationsOnce<TResult>(
528
+ onInvalidation: () => void,
529
+ trackDuring: () => TResult,
530
+ ): ObservationResults<TResult>;
531
+ }
532
+
533
+ /**
534
+ * Results from an operation with tracked observations.
535
+ * @remarks
536
+ * Results from {@link (TreeAlpha:interface).trackObservations} or {@link (TreeAlpha:interface).trackObservationsOnce}.
537
+ * @sealed @alpha
538
+ */
539
+ export interface ObservationResults<TResult> {
540
+ /**
541
+ * The result of the operation which had its observations tracked.
542
+ */
543
+ readonly result: TResult;
544
+
545
+ /**
546
+ * Call to unsubscribe from further invalidations.
547
+ */
548
+ readonly unsubscribe: () => void;
549
+ }
550
+
551
+ /**
552
+ * Subscription to changes on a single node.
553
+ * @remarks
554
+ * Either tracks some set of fields, or all fields and can be updated to track more fields.
555
+ */
556
+ class NodeSubscription {
557
+ /**
558
+ * If undefined, subscribes to all keys.
559
+ * Otherwise only subscribes to the keys in the set.
560
+ */
561
+ private keys: Set<FieldKey> | undefined;
562
+ private readonly unsubscribe: () => void;
563
+ private constructor(
564
+ private readonly onInvalidation: () => void,
565
+ flexNode: FlexTreeNode,
566
+ ) {
567
+ // TODO:Performance: It is possible to optimize this to not use the public TreeNode API.
568
+ const node = getOrCreateNodeFromInnerNode(flexNode);
569
+ assert(node instanceof TreeNode, 0xc54 /* Unexpected leaf value */);
570
+
571
+ const handler = (data: NodeChangedData): void => {
572
+ if (this.keys === undefined || data.changedProperties === undefined) {
573
+ this.onInvalidation();
574
+ } else {
575
+ let keyMap: ReadonlyMap<FieldKey, string> | undefined;
576
+ const schema = treeNodeApi.schema(node);
577
+ if (isObjectNodeSchema(schema)) {
578
+ keyMap = schema.storedKeyToPropertyKey;
579
+ }
580
+ // TODO:Performance: Ideally this would use Set.prototype.isDisjointFrom when available.
581
+ for (const flexKey of this.keys) {
582
+ // TODO:Performance: doing everything at the flex tree layer could avoid this translation
583
+ const key = keyMap?.get(flexKey) ?? flexKey;
584
+
585
+ if (data.changedProperties.has(key)) {
586
+ this.onInvalidation();
587
+ return;
588
+ }
589
+ }
590
+ }
591
+ };
592
+ this.unsubscribe = TreeBeta.on(node, "nodeChanged", handler);
593
+ }
594
+
595
+ /**
596
+ * Create an {@link Observer} which subscribes to what was observed in {@link NodeSubscription}s.
597
+ */
598
+ public static createObserver(
599
+ invalidate: () => void,
600
+ onlyOnce = false,
601
+ ): { observer: Observer; unsubscribe: () => void } {
602
+ const subscriptions = new Map<FlexTreeNode, NodeSubscription>();
603
+ const observer: Observer = {
604
+ observeNodeFields(flexNode: FlexTreeNode): void {
605
+ if (flexNode.value !== undefined) {
606
+ // Leaf value, nothing to observe.
607
+ return;
608
+ }
609
+ const subscription = subscriptions.get(flexNode);
610
+ if (subscription !== undefined) {
611
+ // Already subscribed to this node.
612
+ subscription.keys = undefined; // Now subscribed to all keys.
613
+ } else {
614
+ const newSubscription = new NodeSubscription(invalidate, flexNode);
615
+ subscriptions.set(flexNode, newSubscription);
616
+ }
617
+ },
618
+ observeNodeField(flexNode: FlexTreeNode, key: FieldKey): void {
619
+ if (flexNode.value !== undefined) {
620
+ // Leaf value, nothing to observe.
621
+ return;
622
+ }
623
+ const subscription = subscriptions.get(flexNode);
624
+ if (subscription !== undefined) {
625
+ // Already subscribed to this node: if not subscribed to all keys, subscribe to this one.
626
+ // TODO:Performance: due to how JavaScript set ordering works,
627
+ // it might be faster to check `has` and only add if not present in case the same field is viewed many times.
628
+ subscription.keys?.add(key);
629
+ } else {
630
+ const newSubscription = new NodeSubscription(invalidate, flexNode);
631
+ newSubscription.keys = new Set([key]);
632
+ subscriptions.set(flexNode, newSubscription);
633
+ }
634
+ },
635
+ observeParentOf(node: FlexTreeNode): void {
636
+ // Supporting parent tracking is more difficult that it might seem at first.
637
+ // There are two main complicating factors:
638
+ // 1. The parent may be undefined (the node is a root).
639
+ // 2. If tracking this by subscribing to the parent's changes, then which events are subscribed to needs to be updated after the parent changes.
640
+ //
641
+ // If not supporting the first case (undefined parents), the second case gets problematic: edits which un-parent a node could error due to being unable to update the event subscription.
642
+ // For now this is mitigated by only supporting one of tracking (non-undefined) parents or maintaining event subscriptions across edits.
643
+
644
+ if (!onlyOnce) {
645
+ // TODO: better APIS should be provided which make handling this case practical.
646
+ throw new UsageError("Observation tracking for parents is currently not supported.");
647
+ }
648
+
649
+ const parent = withObservation(undefined, () => node.parentField.parent);
650
+
651
+ if (parent.parent === undefined) {
652
+ // TODO: better APIS should be provided which make handling this case practical.
653
+ throw new UsageError(
654
+ "Observation tracking for parents is currently not supported when parent is undefined.",
655
+ );
656
+ }
657
+ observer.observeNodeField(parent.parent, parent.key);
658
+ },
659
+ };
660
+
661
+ let subscribed = true;
662
+
663
+ return {
664
+ observer,
665
+ unsubscribe: () => {
666
+ if (!subscribed) {
667
+ throw new UsageError("Already unsubscribed");
668
+ }
669
+ subscribed = false;
670
+ for (const subscription of subscriptions.values()) {
671
+ subscription.unsubscribe();
672
+ }
673
+ },
674
+ };
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Handles both {@link (TreeAlpha:interface).trackObservations} and {@link (TreeAlpha:interface).trackObservationsOnce}.
680
+ */
681
+ function trackObservations<TResult>(
682
+ onInvalidation: () => void,
683
+ trackDuring: () => TResult,
684
+ onlyOnce = false,
685
+ ): ObservationResults<TResult> {
686
+ let observing = true;
687
+
688
+ const invalidate = (): void => {
689
+ if (observing) {
690
+ throw new UsageError("Cannot invalidate while tracking observations");
691
+ }
692
+ onInvalidation();
693
+ };
694
+
695
+ const { observer, unsubscribe } = NodeSubscription.createObserver(invalidate, onlyOnce);
696
+ const result = withObservation(observer, trackDuring);
697
+ observing = false;
698
+
699
+ return {
700
+ result,
701
+ unsubscribe,
702
+ };
422
703
  }
423
704
 
424
705
  /**
@@ -427,6 +708,30 @@ export interface TreeAlpha {
427
708
  * @alpha
428
709
  */
429
710
  export const TreeAlpha: TreeAlpha = {
711
+ trackObservations<TResult>(
712
+ onInvalidation: () => void,
713
+ trackDuring: () => TResult,
714
+ ): ObservationResults<TResult> {
715
+ return trackObservations(onInvalidation, trackDuring);
716
+ },
717
+
718
+ trackObservationsOnce<TResult>(
719
+ onInvalidation: () => void,
720
+ trackDuring: () => TResult,
721
+ ): ObservationResults<TResult> {
722
+ const result = trackObservations(
723
+ () => {
724
+ // trackObservations ensures no invalidation occurs while its running,
725
+ // so this callback can only run after trackObservations has returns and thus result is defined.
726
+ result.unsubscribe();
727
+ onInvalidation();
728
+ },
729
+ trackDuring,
730
+ true,
731
+ );
732
+ return result;
733
+ },
734
+
430
735
  branch(node: TreeNode): TreeBranch | undefined {
431
736
  const kernel = getKernel(node);
432
737
  if (!kernel.isHydrated()) {
@@ -523,7 +828,7 @@ export const TreeAlpha: TreeAlpha = {
523
828
  ): JsonCompatible<IFluidHandle> {
524
829
  const schema = tryGetSchema(node) ?? fail(0xacf /* invalid input */);
525
830
  const format = fluidVersionToFieldBatchCodecWriteVersion(options.oldestCompatibleClient);
526
- const codec = makeFieldBatchCodec({ jsonValidator: noopValidator }, format);
831
+ const codec = makeFieldBatchCodec({ jsonValidator: FormatValidatorNoOp }, format);
527
832
  const cursor = borrowFieldCursorFromTreeNodeOrValue(node);
528
833
  const batch: FieldBatch = [cursor];
529
834
  // If none provided, create a compressor which will not compress anything.
@@ -11,7 +11,7 @@ import {
11
11
  UsageError,
12
12
  type ITelemetryLoggerExt,
13
13
  } from "@fluidframework/telemetry-utils/internal";
14
- import { FluidClientVersion, noopValidator } from "../codec/index.js";
14
+ import { FluidClientVersion, FormatValidatorNoOp } from "../codec/index.js";
15
15
  import {
16
16
  type Anchor,
17
17
  type AnchorLocator,
@@ -297,7 +297,7 @@ export function createTreeCheckout(
297
297
  const schema = args?.schema ?? new TreeStoredSchemaRepository();
298
298
  const forest = args?.forest ?? buildForest(breaker, schema);
299
299
  const defaultCodecOptions = {
300
- jsonValidator: noopValidator,
300
+ jsonValidator: FormatValidatorNoOp,
301
301
  oldestCompatibleClient: FluidClientVersion.v2_0,
302
302
  };
303
303
  const defaultFieldBatchVersion = 1;
@@ -789,7 +789,10 @@ export class TreeCheckout implements ITreeCheckoutFork {
789
789
  branch: SharedTreeBranch<SharedTreeEditBuilder, SharedTreeChange>,
790
790
  ): void {
791
791
  // TODO: Dispose old branch, if necessary
792
- assert(!this.#transaction.isInProgress(), "Cannot switch branches during a transaction");
792
+ assert(
793
+ !this.#transaction.isInProgress(),
794
+ 0xc55 /* Cannot switch branches during a transaction */,
795
+ );
793
796
  const diff = diffHistories(
794
797
  this.changeFamily.rebaser,
795
798
  this.#transaction.branch.getHead(),