@fluidframework/tree 2.61.0-355651 → 2.61.0-355781

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 (36) hide show
  1. package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  2. package/dist/feature-libraries/flex-tree/lazyNode.js +4 -3
  3. package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  4. package/dist/feature-libraries/flex-tree/observer.d.ts +3 -1
  5. package/dist/feature-libraries/flex-tree/observer.d.ts.map +1 -1
  6. package/dist/feature-libraries/flex-tree/observer.js.map +1 -1
  7. package/dist/packageVersion.d.ts +1 -1
  8. package/dist/packageVersion.js +1 -1
  9. package/dist/packageVersion.js.map +1 -1
  10. package/dist/shared-tree/treeAlpha.d.ts.map +1 -1
  11. package/dist/shared-tree/treeAlpha.js +67 -10
  12. package/dist/shared-tree/treeAlpha.js.map +1 -1
  13. package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  14. package/dist/simple-tree/core/unhydratedFlexTree.js +3 -3
  15. package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  16. package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  17. package/lib/feature-libraries/flex-tree/lazyNode.js +4 -3
  18. package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  19. package/lib/feature-libraries/flex-tree/observer.d.ts +3 -1
  20. package/lib/feature-libraries/flex-tree/observer.d.ts.map +1 -1
  21. package/lib/feature-libraries/flex-tree/observer.js.map +1 -1
  22. package/lib/packageVersion.d.ts +1 -1
  23. package/lib/packageVersion.js +1 -1
  24. package/lib/packageVersion.js.map +1 -1
  25. package/lib/shared-tree/treeAlpha.d.ts.map +1 -1
  26. package/lib/shared-tree/treeAlpha.js +67 -10
  27. package/lib/shared-tree/treeAlpha.js.map +1 -1
  28. package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  29. package/lib/simple-tree/core/unhydratedFlexTree.js +3 -3
  30. package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  31. package/package.json +20 -20
  32. package/src/feature-libraries/flex-tree/lazyNode.ts +5 -3
  33. package/src/feature-libraries/flex-tree/observer.ts +3 -1
  34. package/src/packageVersion.ts +1 -1
  35. package/src/shared-tree/treeAlpha.ts +80 -12
  36. package/src/simple-tree/core/unhydratedFlexTree.ts +5 -4
@@ -55,6 +55,7 @@ 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 {
@@ -63,7 +64,7 @@ import {
63
64
  type ICodecOptions,
64
65
  type CodecWriteOptions,
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,
@@ -432,6 +433,46 @@ export interface TreeAlpha {
432
433
  ): { result: TResult; unsubscribe: () => void };
433
434
  }
434
435
 
436
+ class NodeSubscription {
437
+ /**
438
+ * If undefined, subscribes to all keys.
439
+ * Otherwise only subscribes to the keys in the set.
440
+ */
441
+ public keys: Set<FieldKey> | undefined;
442
+ public readonly unsubscribe: () => void;
443
+ public constructor(
444
+ public readonly onInvalidation: () => void,
445
+ flexNode: FlexTreeNode,
446
+ ) {
447
+ // TODO:Performance: It is possible to optimize this to not use the public TreeNode API.
448
+ const node = getOrCreateNodeFromInnerNode(flexNode);
449
+ assert(node instanceof TreeNode, "Unexpected leaf value");
450
+
451
+ const handler = (data: NodeChangedData): void => {
452
+ if (this.keys === undefined || data.changedProperties === undefined) {
453
+ this.onInvalidation();
454
+ } else {
455
+ let keyMap: ReadonlyMap<FieldKey, string> | undefined;
456
+ const schema = treeNodeApi.schema(node);
457
+ if (isObjectNodeSchema(schema)) {
458
+ keyMap = schema.storedKeyToPropertyKey;
459
+ }
460
+ // TODO:Performance: Ideally this would use Set.prototype.isDisjointFrom when available.
461
+ for (const flexKey of this.keys) {
462
+ // TODO:Performance: doing everything at the flex tree layer could avoid this translation
463
+ const key = keyMap?.get(flexKey) ?? flexKey;
464
+
465
+ if (data.changedProperties.has(key)) {
466
+ this.onInvalidation();
467
+ return;
468
+ }
469
+ }
470
+ }
471
+ };
472
+ this.unsubscribe = TreeBeta.on(node, "nodeChanged", handler);
473
+ }
474
+ }
475
+
435
476
  /**
436
477
  * Extensions to {@link (Tree:variable)} and {@link (TreeBeta:variable)} which are not yet stable.
437
478
  * @see {@link (TreeAlpha:interface)}.
@@ -442,20 +483,46 @@ export const TreeAlpha: TreeAlpha = {
442
483
  onInvalidation: () => void,
443
484
  trackDuring: () => TResult,
444
485
  ): { result: TResult; unsubscribe: () => void } {
445
- const subscriptions = new Map<FlexTreeNode | TreeBranch, () => void>();
486
+ let observing = true;
487
+
488
+ const invalidate = (): void => {
489
+ if (observing) {
490
+ throw new UsageError("Cannot invalidate while tracking observations");
491
+ }
492
+ onInvalidation();
493
+ };
494
+
495
+ const subscriptions = new Map<FlexTreeNode, NodeSubscription>();
446
496
  const observer: Observer = {
447
- observeNodeContent(flexNode: FlexTreeNode): void {
497
+ observeNodeFields(flexNode: FlexTreeNode): void {
498
+ if (flexNode.value !== undefined) {
499
+ // Leaf value, nothing to observe.
500
+ return;
501
+ }
502
+ const subscription = subscriptions.get(flexNode);
503
+ if (subscription !== undefined) {
504
+ // Already subscribed to this node.
505
+ subscription.keys = undefined; // Now subscribed to all keys.
506
+ } else {
507
+ const newSubscription = new NodeSubscription(invalidate, flexNode);
508
+ subscriptions.set(flexNode, newSubscription);
509
+ }
510
+ },
511
+ observeNodeField(flexNode: FlexTreeNode, key: FieldKey): void {
448
512
  if (flexNode.value !== undefined) {
449
513
  // Leaf value, nothing to observe.
450
514
  return;
451
515
  }
452
- if (!subscriptions.has(flexNode)) {
453
- const node = getOrCreateNodeFromInnerNode(flexNode);
454
- assert(node instanceof TreeNode, "Unexpected leaf value");
455
- const unsubscribe = treeNodeApi.on(node, "nodeChanged", () => {
456
- onInvalidation();
457
- });
458
- subscriptions.set(flexNode, unsubscribe);
516
+ const subscription = subscriptions.get(flexNode);
517
+ if (subscription !== undefined) {
518
+ // Already subscribed to this node: if not subscribed to all keys, subscribe to this one.
519
+ // TODO:Performance: due to how JavaScript set ordering works,
520
+ // it might be faster to check `has` and only add if not present in case the same field is viewed many times.
521
+ subscription.keys?.add(key);
522
+ } else {
523
+ const newSubscription = new NodeSubscription(invalidate, flexNode);
524
+ newSubscription.keys = new Set([key]);
525
+ subscriptions.set(flexNode, newSubscription);
459
526
  }
460
527
  },
461
528
  observeParentOf(node: FlexTreeNode): void {
@@ -465,6 +532,7 @@ export const TreeAlpha: TreeAlpha = {
465
532
  },
466
533
  };
467
534
  const result = withObservation(observer, trackDuring);
535
+ observing = false;
468
536
 
469
537
  let subscribed = true;
470
538
 
@@ -475,8 +543,8 @@ export const TreeAlpha: TreeAlpha = {
475
543
  throw new UsageError("Already unsubscribed");
476
544
  }
477
545
  subscribed = false;
478
- for (const unsubscribe of subscriptions.values()) {
479
- unsubscribe();
546
+ for (const subscription of subscriptions.values()) {
547
+ subscription.unsubscribe();
480
548
  }
481
549
  },
482
550
  };
@@ -146,7 +146,7 @@ export class UnhydratedFlexTreeNode
146
146
  public readonly fields: MinimalFieldMap<UnhydratedFlexTreeField> = {
147
147
  get: (key: FieldKey): UnhydratedFlexTreeField | undefined => this.tryGetField(key),
148
148
  [Symbol.iterator]: (): IterableIterator<[FieldKey, UnhydratedFlexTreeField]> => {
149
- currentObserver?.observeNodeContent(this);
149
+ currentObserver?.observeNodeFields(this);
150
150
  return filterIterable(this.fieldsAll, ([, field]) => field.length > 0);
151
151
  },
152
152
  };
@@ -230,7 +230,7 @@ export class UnhydratedFlexTreeNode
230
230
  }
231
231
 
232
232
  public tryGetField(key: FieldKey): UnhydratedFlexTreeField | undefined {
233
- currentObserver?.observeNodeContent(this);
233
+ currentObserver?.observeNodeField(this, key);
234
234
 
235
235
  const field = this.fieldsAll.get(key);
236
236
  // Only return the field if it is not empty, in order to fulfill the contract of `tryGetField`.
@@ -240,9 +240,10 @@ export class UnhydratedFlexTreeNode
240
240
  }
241
241
 
242
242
  public getBoxed(key: string): UnhydratedFlexTreeField {
243
- currentObserver?.observeNodeContent(this);
244
-
245
243
  const fieldKey: FieldKey = brand(key);
244
+
245
+ currentObserver?.observeNodeField(this, fieldKey);
246
+
246
247
  return this.getOrCreateField(fieldKey);
247
248
  }
248
249