@fluidframework/tree 2.61.0-355781 → 2.61.0-355990

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 (32) hide show
  1. package/api-report/tree.alpha.api.md +4 -0
  2. package/dist/feature-libraries/flex-tree/observer.d.ts +1 -1
  3. package/dist/feature-libraries/flex-tree/observer.d.ts.map +1 -1
  4. package/dist/feature-libraries/flex-tree/observer.js.map +1 -1
  5. package/dist/packageVersion.d.ts +1 -1
  6. package/dist/packageVersion.js +1 -1
  7. package/dist/packageVersion.js.map +1 -1
  8. package/dist/shared-tree/treeAlpha.d.ts +20 -0
  9. package/dist/shared-tree/treeAlpha.d.ts.map +1 -1
  10. package/dist/shared-tree/treeAlpha.js +86 -62
  11. package/dist/shared-tree/treeAlpha.js.map +1 -1
  12. package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  13. package/dist/shared-tree-core/sharedTreeCore.js +8 -12
  14. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  15. package/lib/feature-libraries/flex-tree/observer.d.ts +1 -1
  16. package/lib/feature-libraries/flex-tree/observer.d.ts.map +1 -1
  17. package/lib/feature-libraries/flex-tree/observer.js.map +1 -1
  18. package/lib/packageVersion.d.ts +1 -1
  19. package/lib/packageVersion.js +1 -1
  20. package/lib/packageVersion.js.map +1 -1
  21. package/lib/shared-tree/treeAlpha.d.ts +20 -0
  22. package/lib/shared-tree/treeAlpha.d.ts.map +1 -1
  23. package/lib/shared-tree/treeAlpha.js +86 -62
  24. package/lib/shared-tree/treeAlpha.js.map +1 -1
  25. package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  26. package/lib/shared-tree-core/sharedTreeCore.js +9 -13
  27. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  28. package/package.json +20 -20
  29. package/src/feature-libraries/flex-tree/observer.ts +3 -3
  30. package/src/packageVersion.ts +1 -1
  31. package/src/shared-tree/treeAlpha.ts +126 -63
  32. package/src/shared-tree-core/sharedTreeCore.ts +9 -13
@@ -426,11 +426,32 @@ export interface TreeAlpha {
426
426
 
427
427
  /**
428
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
+ * Even after onInvalidation is called, these subscriptions remain active until `unsubscribe` is called.
439
+ * See {@link (TreeAlpha:interface).trackObservationsOnce} for a version which automatically unsubscribes on the first invalidation.
429
440
  */
430
441
  trackObservations<TResult>(
431
442
  onInvalidation: () => void,
432
443
  trackDuring: () => TResult,
433
444
  ): { result: TResult; unsubscribe: () => void };
445
+
446
+ /**
447
+ * {@link (TreeAlpha:interface).trackObservations} except automatically unsubscribes when the first invalidation occurs.
448
+ * @remarks
449
+ * This also supports tracking parentage, unlike {@link (TreeAlpha:interface).trackObservations}, as long as the parent is not undefined.
450
+ */
451
+ trackObservationsOnce<TResult>(
452
+ onInvalidation: () => void,
453
+ trackDuring: () => TResult,
454
+ ): { result: TResult; unsubscribe: () => void };
434
455
  }
435
456
 
436
457
  class NodeSubscription {
@@ -473,6 +494,97 @@ class NodeSubscription {
473
494
  }
474
495
  }
475
496
 
497
+ function trackObservations<TResult>(
498
+ onInvalidation: () => void,
499
+ trackDuring: () => TResult,
500
+ onlyOnce = false,
501
+ ): { result: TResult; unsubscribe: () => void } {
502
+ let observing = true;
503
+
504
+ const invalidate = (): void => {
505
+ if (observing) {
506
+ throw new UsageError("Cannot invalidate while tracking observations");
507
+ }
508
+ onInvalidation();
509
+ };
510
+
511
+ const subscriptions = new Map<FlexTreeNode, NodeSubscription>();
512
+ const observer: Observer = {
513
+ observeNodeFields(flexNode: FlexTreeNode): void {
514
+ if (flexNode.value !== undefined) {
515
+ // Leaf value, nothing to observe.
516
+ return;
517
+ }
518
+ const subscription = subscriptions.get(flexNode);
519
+ if (subscription !== undefined) {
520
+ // Already subscribed to this node.
521
+ subscription.keys = undefined; // Now subscribed to all keys.
522
+ } else {
523
+ const newSubscription = new NodeSubscription(invalidate, flexNode);
524
+ subscriptions.set(flexNode, newSubscription);
525
+ }
526
+ },
527
+ observeNodeField(flexNode: FlexTreeNode, key: FieldKey): void {
528
+ if (flexNode.value !== undefined) {
529
+ // Leaf value, nothing to observe.
530
+ return;
531
+ }
532
+ const subscription = subscriptions.get(flexNode);
533
+ if (subscription !== undefined) {
534
+ // Already subscribed to this node: if not subscribed to all keys, subscribe to this one.
535
+ // TODO:Performance: due to how JavaScript set ordering works,
536
+ // it might be faster to check `has` and only add if not present in case the same field is viewed many times.
537
+ subscription.keys?.add(key);
538
+ } else {
539
+ const newSubscription = new NodeSubscription(invalidate, flexNode);
540
+ newSubscription.keys = new Set([key]);
541
+ subscriptions.set(flexNode, newSubscription);
542
+ }
543
+ },
544
+ observeParentOf(node: FlexTreeNode): void {
545
+ // Supporting parent tracking is more difficult that it might seem at first.
546
+ // There are two main complicating factors:
547
+ // 1. The parent may be undefined (the node is a root).
548
+ // 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.
549
+ //
550
+ // If not supporting the first case (undefined parents), the second case gets problematic since it would result in edits which take a node who's parent was observed,
551
+ // and un-parent it, could then throw a usage error.
552
+
553
+ if (!onlyOnce) {
554
+ // TODO: better APIS should be provided which make handling this case practical.
555
+ throw new UsageError("Observation tracking for parents is currently not supported.");
556
+ }
557
+
558
+ const parent = withObservation(undefined, () => node.parentField.parent);
559
+
560
+ if (parent.parent === undefined) {
561
+ // TODO: better APIS should be provided which make handling this case practical.
562
+ throw new UsageError(
563
+ "Observation tracking for parents is currently not supported when parent is undefined.",
564
+ );
565
+ }
566
+ observer.observeNodeField(parent.parent, parent.key);
567
+ },
568
+ };
569
+ const result = withObservation(observer, trackDuring);
570
+ observing = false;
571
+
572
+ let subscribed = true;
573
+
574
+ return {
575
+ result,
576
+ unsubscribe: () => {
577
+ if (!subscribed) {
578
+ throw new UsageError("Already unsubscribed");
579
+ }
580
+ subscribed = false;
581
+ for (const subscription of subscriptions.values()) {
582
+ subscription.unsubscribe();
583
+ }
584
+ },
585
+ };
586
+ }
587
+
476
588
  /**
477
589
  * Extensions to {@link (Tree:variable)} and {@link (TreeBeta:variable)} which are not yet stable.
478
590
  * @see {@link (TreeAlpha:interface)}.
@@ -483,71 +595,22 @@ export const TreeAlpha: TreeAlpha = {
483
595
  onInvalidation: () => void,
484
596
  trackDuring: () => TResult,
485
597
  ): { result: TResult; unsubscribe: () => 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>();
496
- const observer: Observer = {
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 {
512
- if (flexNode.value !== undefined) {
513
- // Leaf value, nothing to observe.
514
- return;
515
- }
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);
526
- }
527
- },
528
- observeParentOf(node: FlexTreeNode): void {
529
- throw new UsageError("Observation tracking for parents is currently not supported.");
530
- // This is conservatively correct, but could be optimized.
531
- // observer.observeNodeContent(parent);
532
- },
533
- };
534
- const result = withObservation(observer, trackDuring);
535
- observing = false;
536
-
537
- let subscribed = true;
598
+ return trackObservations(onInvalidation, trackDuring);
599
+ },
538
600
 
539
- return {
540
- result,
541
- unsubscribe: () => {
542
- if (!subscribed) {
543
- throw new UsageError("Already unsubscribed");
544
- }
545
- subscribed = false;
546
- for (const subscription of subscriptions.values()) {
547
- subscription.unsubscribe();
548
- }
601
+ trackObservationsOnce<TResult>(
602
+ onInvalidation: () => void,
603
+ trackDuring: () => TResult,
604
+ ): { result: TResult; unsubscribe: () => void } {
605
+ const result = trackObservations(
606
+ () => {
607
+ result.unsubscribe();
608
+ onInvalidation();
549
609
  },
550
- };
610
+ trackDuring,
611
+ true,
612
+ );
613
+ return result;
551
614
  },
552
615
 
553
616
  branch(node: TreeNode): TreeBranch | undefined {
@@ -25,7 +25,6 @@ import type { ICodecOptions, IJsonCodec } from "../codec/index.js";
25
25
  import {
26
26
  type ChangeFamily,
27
27
  type ChangeFamilyEditor,
28
- findAncestor,
29
28
  type GraphCommit,
30
29
  type RevisionTag,
31
30
  RevisionTagCodec,
@@ -259,18 +258,15 @@ export class SharedTreeCore<TEditor extends ChangeFamilyEditor, TChange>
259
258
  // If we are detached but loading from a summary, then we need to update our detached revision to ensure that it is ahead of all detached revisions in the summary.
260
259
  // First, finish loading the edit manager so that we can inspect the sequence numbers of the commits on the trunk.
261
260
  await loadEditManager;
262
- // Find the most recent detached revision in the summary trunk...
263
- let latestDetachedSequenceNumber: SeqNumber | undefined;
264
- findAncestor(this.editManager.getTrunkHead(), (c) => {
265
- const sequenceNumber = this.editManager.getSequenceNumber(c);
266
- if (sequenceNumber !== undefined && sequenceNumber < 0) {
267
- latestDetachedSequenceNumber = sequenceNumber;
268
- return true;
269
- }
270
- return false;
271
- });
272
- // ...and set our detached revision to be as it would be if we had been already created that revision.
273
- this.detachedRevision = latestDetachedSequenceNumber ?? this.detachedRevision;
261
+
262
+ const head = this.editManager.getTrunkHead();
263
+ const latestDetachedSequenceNumber = this.editManager.getSequenceNumber(head);
264
+ // When we load a summary for a tree that was never attached,
265
+ // latestDetachedSequenceNumber is either undefined (no commits in summary) or negative (all commits in summary were made while detached).
266
+ // We only need to update `this.detachedRevision` in the latter case.
267
+ if (latestDetachedSequenceNumber !== undefined && latestDetachedSequenceNumber < 0) {
268
+ this.detachedRevision = latestDetachedSequenceNumber;
269
+ }
274
270
  await Promise.all(loadSummarizables);
275
271
  } else {
276
272
  await Promise.all([loadEditManager, ...loadSummarizables]);