@fluidframework/tree 2.61.0-356132 → 2.61.0-356312

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 (111) hide show
  1. package/api-report/tree.alpha.api.md +14 -8
  2. package/dist/alpha.d.ts +3 -0
  3. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +0 -2
  4. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  5. package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  6. package/dist/feature-libraries/flex-tree/index.d.ts +0 -1
  7. package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
  8. package/dist/feature-libraries/flex-tree/index.js +1 -4
  9. package/dist/feature-libraries/flex-tree/index.js.map +1 -1
  10. package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  11. package/dist/feature-libraries/flex-tree/lazyNode.js +8 -15
  12. package/dist/feature-libraries/flex-tree/lazyNode.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 +1 -3
  16. package/dist/feature-libraries/index.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/packageVersion.d.ts +1 -1
  22. package/dist/packageVersion.js +1 -1
  23. package/dist/packageVersion.js.map +1 -1
  24. package/dist/shared-tree/treeAlpha.d.ts +1 -28
  25. package/dist/shared-tree/treeAlpha.d.ts.map +1 -1
  26. package/dist/shared-tree/treeAlpha.js +0 -139
  27. package/dist/shared-tree/treeAlpha.js.map +1 -1
  28. package/dist/simple-tree/api/dirtyIndex.d.ts +43 -0
  29. package/dist/simple-tree/api/dirtyIndex.d.ts.map +1 -0
  30. package/dist/simple-tree/api/dirtyIndex.js +122 -0
  31. package/dist/simple-tree/api/dirtyIndex.js.map +1 -0
  32. package/dist/simple-tree/api/index.d.ts +1 -0
  33. package/dist/simple-tree/api/index.d.ts.map +1 -1
  34. package/dist/simple-tree/api/index.js +3 -1
  35. package/dist/simple-tree/api/index.js.map +1 -1
  36. package/dist/simple-tree/api/tree.d.ts +4 -3
  37. package/dist/simple-tree/api/tree.d.ts.map +1 -1
  38. package/dist/simple-tree/api/tree.js.map +1 -1
  39. package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  40. package/dist/simple-tree/core/unhydratedFlexTree.js +1 -7
  41. package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  42. package/dist/simple-tree/index.d.ts +1 -1
  43. package/dist/simple-tree/index.d.ts.map +1 -1
  44. package/dist/simple-tree/index.js +3 -2
  45. package/dist/simple-tree/index.js.map +1 -1
  46. package/lib/alpha.d.ts +3 -0
  47. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +0 -2
  48. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  49. package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  50. package/lib/feature-libraries/flex-tree/index.d.ts +0 -1
  51. package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
  52. package/lib/feature-libraries/flex-tree/index.js +0 -1
  53. package/lib/feature-libraries/flex-tree/index.js.map +1 -1
  54. package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  55. package/lib/feature-libraries/flex-tree/lazyNode.js +8 -15
  56. package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  57. package/lib/feature-libraries/index.d.ts +1 -1
  58. package/lib/feature-libraries/index.d.ts.map +1 -1
  59. package/lib/feature-libraries/index.js +1 -1
  60. package/lib/feature-libraries/index.js.map +1 -1
  61. package/lib/index.d.ts +1 -1
  62. package/lib/index.d.ts.map +1 -1
  63. package/lib/index.js +1 -1
  64. package/lib/index.js.map +1 -1
  65. package/lib/packageVersion.d.ts +1 -1
  66. package/lib/packageVersion.js +1 -1
  67. package/lib/packageVersion.js.map +1 -1
  68. package/lib/shared-tree/treeAlpha.d.ts +1 -28
  69. package/lib/shared-tree/treeAlpha.d.ts.map +1 -1
  70. package/lib/shared-tree/treeAlpha.js +2 -141
  71. package/lib/shared-tree/treeAlpha.js.map +1 -1
  72. package/lib/simple-tree/api/dirtyIndex.d.ts +43 -0
  73. package/lib/simple-tree/api/dirtyIndex.d.ts.map +1 -0
  74. package/lib/simple-tree/api/dirtyIndex.js +118 -0
  75. package/lib/simple-tree/api/dirtyIndex.js.map +1 -0
  76. package/lib/simple-tree/api/index.d.ts +1 -0
  77. package/lib/simple-tree/api/index.d.ts.map +1 -1
  78. package/lib/simple-tree/api/index.js +1 -0
  79. package/lib/simple-tree/api/index.js.map +1 -1
  80. package/lib/simple-tree/api/tree.d.ts +4 -3
  81. package/lib/simple-tree/api/tree.d.ts.map +1 -1
  82. package/lib/simple-tree/api/tree.js.map +1 -1
  83. package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  84. package/lib/simple-tree/core/unhydratedFlexTree.js +2 -8
  85. package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  86. package/lib/simple-tree/index.d.ts +1 -1
  87. package/lib/simple-tree/index.d.ts.map +1 -1
  88. package/lib/simple-tree/index.js +1 -1
  89. package/lib/simple-tree/index.js.map +1 -1
  90. package/package.json +20 -20
  91. package/src/feature-libraries/flex-tree/flexTreeTypes.ts +0 -2
  92. package/src/feature-libraries/flex-tree/index.ts +0 -2
  93. package/src/feature-libraries/flex-tree/lazyNode.ts +3 -13
  94. package/src/feature-libraries/index.ts +0 -3
  95. package/src/index.ts +3 -0
  96. package/src/packageVersion.ts +1 -1
  97. package/src/shared-tree/treeAlpha.ts +2 -213
  98. package/src/simple-tree/api/dirtyIndex.ts +155 -0
  99. package/src/simple-tree/api/index.ts +5 -0
  100. package/src/simple-tree/api/tree.ts +4 -3
  101. package/src/simple-tree/core/unhydratedFlexTree.ts +2 -11
  102. package/src/simple-tree/index.ts +3 -0
  103. package/dist/feature-libraries/flex-tree/observer.d.ts +0 -27
  104. package/dist/feature-libraries/flex-tree/observer.d.ts.map +0 -1
  105. package/dist/feature-libraries/flex-tree/observer.js +0 -33
  106. package/dist/feature-libraries/flex-tree/observer.js.map +0 -1
  107. package/lib/feature-libraries/flex-tree/observer.d.ts +0 -27
  108. package/lib/feature-libraries/flex-tree/observer.d.ts.map +0 -1
  109. package/lib/feature-libraries/flex-tree/observer.js +0 -35
  110. package/lib/feature-libraries/flex-tree/observer.js.map +0 -1
  111. package/src/feature-libraries/flex-tree/observer.ts +0 -58
package/src/index.ts CHANGED
@@ -145,6 +145,9 @@ export {
145
145
  type IdentifierIndex,
146
146
  createSimpleTreeIndex,
147
147
  createIdentifierIndex,
148
+ type DirtyTreeStatus,
149
+ trackDirtyNodes,
150
+ type DirtyTreeMap,
148
151
  // experimental @alpha APIs:
149
152
  adaptEnum,
150
153
  enumFromStrings,
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/tree";
9
- export const pkgVersion = "2.61.0-356132";
9
+ export const pkgVersion = "2.61.0-356312";
@@ -17,7 +17,7 @@ import type { IIdCompressor } from "@fluidframework/id-compressor";
17
17
  import {
18
18
  asIndex,
19
19
  getKernel,
20
- TreeNode,
20
+ type TreeNode,
21
21
  type Unhydrated,
22
22
  TreeBeta,
23
23
  tryGetSchema,
@@ -55,7 +55,6 @@ import {
55
55
  convertField,
56
56
  toUnhydratedSchema,
57
57
  type TreeParsingOptions,
58
- type NodeChangedData,
59
58
  } from "../simple-tree/index.js";
60
59
  import { brand, extractFromOpaque, type JsonCompatible } from "../util/index.js";
61
60
  import {
@@ -64,7 +63,7 @@ import {
64
63
  type ICodecOptions,
65
64
  type CodecWriteOptions,
66
65
  } from "../codec/index.js";
67
- import { EmptyKey, type FieldKey, type ITreeCursorSynchronous } from "../core/index.js";
66
+ import { EmptyKey, type ITreeCursorSynchronous } from "../core/index.js";
68
67
  import {
69
68
  cursorForMapTreeField,
70
69
  defaultSchemaPolicy,
@@ -77,9 +76,6 @@ import {
77
76
  fluidVersionToFieldBatchCodecWriteVersion,
78
77
  type LocalNodeIdentifier,
79
78
  type FlexTreeSequenceField,
80
- type FlexTreeNode,
81
- type Observer,
82
- withObservation,
83
79
  } from "../feature-libraries/index.js";
84
80
  import { independentInitializedView, type ViewContent } from "./independentView.js";
85
81
  import { SchematizingSimpleTreeView, ViewSlot } from "./schematizingTreeView.js";
@@ -423,189 +419,6 @@ export interface TreeAlpha {
423
419
  children(
424
420
  node: TreeNode,
425
421
  ): 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
- * 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.
440
- */
441
- trackObservations<TResult>(
442
- onInvalidation: () => void,
443
- trackDuring: () => TResult,
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 };
455
- }
456
-
457
- /**
458
- * Subscription to changes on a single node.
459
- * @remarks
460
- * Either tracks some set of fields, or all fields and can be updated to track more fields.
461
- */
462
- class NodeSubscription {
463
- /**
464
- * If undefined, subscribes to all keys.
465
- * Otherwise only subscribes to the keys in the set.
466
- */
467
- private keys: Set<FieldKey> | undefined;
468
- private readonly unsubscribe: () => void;
469
- private constructor(
470
- private readonly onInvalidation: () => void,
471
- flexNode: FlexTreeNode,
472
- ) {
473
- // TODO:Performance: It is possible to optimize this to not use the public TreeNode API.
474
- const node = getOrCreateNodeFromInnerNode(flexNode);
475
- assert(node instanceof TreeNode, "Unexpected leaf value");
476
-
477
- const handler = (data: NodeChangedData): void => {
478
- if (this.keys === undefined || data.changedProperties === undefined) {
479
- this.onInvalidation();
480
- } else {
481
- let keyMap: ReadonlyMap<FieldKey, string> | undefined;
482
- const schema = treeNodeApi.schema(node);
483
- if (isObjectNodeSchema(schema)) {
484
- keyMap = schema.storedKeyToPropertyKey;
485
- }
486
- // TODO:Performance: Ideally this would use Set.prototype.isDisjointFrom when available.
487
- for (const flexKey of this.keys) {
488
- // TODO:Performance: doing everything at the flex tree layer could avoid this translation
489
- const key = keyMap?.get(flexKey) ?? flexKey;
490
-
491
- if (data.changedProperties.has(key)) {
492
- this.onInvalidation();
493
- return;
494
- }
495
- }
496
- }
497
- };
498
- this.unsubscribe = TreeBeta.on(node, "nodeChanged", handler);
499
- }
500
-
501
- /**
502
- * Create an {@link Observer} which subscribes to what was observed in {@link NodeSubscription}s.
503
- */
504
- public static createObserver(
505
- invalidate: () => void,
506
- onlyOnce = false,
507
- ): { observer: Observer; unsubscribe: () => void } {
508
- const subscriptions = new Map<FlexTreeNode, NodeSubscription>();
509
- const observer: Observer = {
510
- observeNodeFields(flexNode: FlexTreeNode): void {
511
- if (flexNode.value !== undefined) {
512
- // Leaf value, nothing to observe.
513
- return;
514
- }
515
- const subscription = subscriptions.get(flexNode);
516
- if (subscription !== undefined) {
517
- // Already subscribed to this node.
518
- subscription.keys = undefined; // Now subscribed to all keys.
519
- } else {
520
- const newSubscription = new NodeSubscription(invalidate, flexNode);
521
- subscriptions.set(flexNode, newSubscription);
522
- }
523
- },
524
- observeNodeField(flexNode: FlexTreeNode, key: FieldKey): void {
525
- if (flexNode.value !== undefined) {
526
- // Leaf value, nothing to observe.
527
- return;
528
- }
529
- const subscription = subscriptions.get(flexNode);
530
- if (subscription !== undefined) {
531
- // Already subscribed to this node: if not subscribed to all keys, subscribe to this one.
532
- // TODO:Performance: due to how JavaScript set ordering works,
533
- // it might be faster to check `has` and only add if not present in case the same field is viewed many times.
534
- subscription.keys?.add(key);
535
- } else {
536
- const newSubscription = new NodeSubscription(invalidate, flexNode);
537
- newSubscription.keys = new Set([key]);
538
- subscriptions.set(flexNode, newSubscription);
539
- }
540
- },
541
- observeParentOf(node: FlexTreeNode): void {
542
- // Supporting parent tracking is more difficult that it might seem at first.
543
- // There are two main complicating factors:
544
- // 1. The parent may be undefined (the node is a root).
545
- // 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.
546
- //
547
- // 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,
548
- // and un-parent it, could then throw a usage error.
549
-
550
- if (!onlyOnce) {
551
- // TODO: better APIS should be provided which make handling this case practical.
552
- throw new UsageError("Observation tracking for parents is currently not supported.");
553
- }
554
-
555
- const parent = withObservation(undefined, () => node.parentField.parent);
556
-
557
- if (parent.parent === undefined) {
558
- // TODO: better APIS should be provided which make handling this case practical.
559
- throw new UsageError(
560
- "Observation tracking for parents is currently not supported when parent is undefined.",
561
- );
562
- }
563
- observer.observeNodeField(parent.parent, parent.key);
564
- },
565
- };
566
-
567
- let subscribed = true;
568
-
569
- return {
570
- observer,
571
- unsubscribe: () => {
572
- if (!subscribed) {
573
- throw new UsageError("Already unsubscribed");
574
- }
575
- subscribed = false;
576
- for (const subscription of subscriptions.values()) {
577
- subscription.unsubscribe();
578
- }
579
- },
580
- };
581
- }
582
- }
583
-
584
- /**
585
- * Handles both {@link (TreeAlpha:interface).trackObservations} and {@link (TreeAlpha:interface).trackObservationsOnce}.
586
- */
587
- function trackObservations<TResult>(
588
- onInvalidation: () => void,
589
- trackDuring: () => TResult,
590
- onlyOnce = false,
591
- ): { result: TResult; unsubscribe: () => void } {
592
- let observing = true;
593
-
594
- const invalidate = (): void => {
595
- if (observing) {
596
- throw new UsageError("Cannot invalidate while tracking observations");
597
- }
598
- onInvalidation();
599
- };
600
-
601
- const { observer, unsubscribe } = NodeSubscription.createObserver(invalidate, onlyOnce);
602
- const result = withObservation(observer, trackDuring);
603
- observing = false;
604
-
605
- return {
606
- result,
607
- unsubscribe,
608
- };
609
422
  }
610
423
 
611
424
  /**
@@ -614,30 +427,6 @@ function trackObservations<TResult>(
614
427
  * @alpha
615
428
  */
616
429
  export const TreeAlpha: TreeAlpha = {
617
- trackObservations<TResult>(
618
- onInvalidation: () => void,
619
- trackDuring: () => TResult,
620
- ): { result: TResult; unsubscribe: () => void } {
621
- return trackObservations(onInvalidation, trackDuring);
622
- },
623
-
624
- trackObservationsOnce<TResult>(
625
- onInvalidation: () => void,
626
- trackDuring: () => TResult,
627
- ): { result: TResult; unsubscribe: () => void } {
628
- const result = trackObservations(
629
- () => {
630
- // trackObservations ensures no invalidation occurs while its running,
631
- // so this callback can only run after trackObservations has returns and thus result is defined.
632
- result.unsubscribe();
633
- onInvalidation();
634
- },
635
- trackDuring,
636
- true,
637
- );
638
- return result;
639
- },
640
-
641
430
  branch(node: TreeNode): TreeBranch | undefined {
642
431
  const kernel = getKernel(node);
643
432
  if (!kernel.isHydrated()) {
@@ -0,0 +1,155 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { assert } from "@fluidframework/core-utils/internal";
7
+ import {
8
+ createAnnouncedVisitor,
9
+ CursorLocationType,
10
+ type AnnouncedVisitor,
11
+ type FieldKey,
12
+ type IEditableForest,
13
+ type UpPath,
14
+ } from "../../core/index.js";
15
+ import { TreeNode, treeNodeFromAnchor } from "../core/index.js";
16
+ import type { TreeViewAlpha } from "./tree.js";
17
+ import type { SchematizingSimpleTreeView } from "../../shared-tree/index.js";
18
+ import type { ImplicitFieldSchema } from "../fieldSchema.js";
19
+
20
+ /**
21
+ * The status of a node in a that has been {@link trackDirtyNodes | tracked for changes}.
22
+ * @remarks Nodes can be marked "new", "changed", or "moved", with "new" taking precedence over "changed" and "changed" taking precedence over "moved".
23
+ * * "new": The node was added.
24
+ * * "changed": A direct child was updated, added, removed, or moved (and the node is not "new").
25
+ * * "moved": The node was moved between/within arrays (and the node is not "new" or "changed").
26
+ * @alpha
27
+ */
28
+ export type DirtyTreeStatus = "new" | "changed" | "moved";
29
+
30
+ /**
31
+ * A map-like interface for tracking the {@link DirtyTreeStatus | status} of nodes that have been {@link trackDirtyNodes | tracked for changes}.
32
+ * @privateRemarks TODO: Replace this with `MapGetSet` from `@fluidframework/core-interfaces`.
33
+ * @alpha
34
+ */
35
+ export interface DirtyTreeMap {
36
+ get(node: TreeNode): DirtyTreeStatus | undefined;
37
+ set(node: TreeNode, status: DirtyTreeStatus): void;
38
+ }
39
+
40
+ /**
41
+ * Registers a visitor on the view's forest that tracks which nodes are dirty.
42
+ * @param view - The view to track dirty nodes on
43
+ * @param dirty - A {@link DirtyTreeMap | map} that will be updated over time to reflect the {@link DirtyTreeStatus | status} of nodes as they change.
44
+ * Nodes that have not changed will not be inserted/updated in the map.
45
+ * @returns a cleanup function that should be called when the tracking is no longer needed.
46
+ * @example
47
+ * ```typescript
48
+ * const dirty = new Map<TreeNode, DirtyTreeStatus>();
49
+ * const stopTracking = trackDirtyNodes(view, dirty);
50
+ * // ... make changes to the view ...
51
+ * console.log(`The root of the tree is ${dirty.get(view.root) ?? "unchanged"}`);
52
+ * stopTracking();
53
+ * ```
54
+ * @alpha
55
+ */
56
+ export function trackDirtyNodes(
57
+ view: TreeViewAlpha<ImplicitFieldSchema>,
58
+ dirty: DirtyTreeMap,
59
+ ): () => void {
60
+ const forest = (view as SchematizingSimpleTreeView<ImplicitFieldSchema>).checkout.forest;
61
+ const announcedVisitor = (): AnnouncedVisitor => createDirtyVisitor(forest, dirty);
62
+ forest.registerAnnouncedVisitor(announcedVisitor);
63
+ return () => {
64
+ forest.deregisterAnnouncedVisitor(announcedVisitor);
65
+ };
66
+ }
67
+
68
+ function createDirtyVisitor(forest: IEditableForest, dirty: DirtyTreeMap): AnnouncedVisitor {
69
+ // When cursor is in Fields mode, `parentField` is the field and `parent` is the parent node above that field (if any).
70
+ // When cursor is in Nodes mode, `parent` is the current node and `parentField` is undefined.
71
+ let parentField: FieldKey | undefined;
72
+ let parent: UpPath | undefined;
73
+
74
+ return createAnnouncedVisitor({
75
+ beforeDetach: (src) => {
76
+ assert(parent !== undefined, "Expected node");
77
+ assert(parentField !== undefined, "Expected field");
78
+ for (let parentIndex = src.start; parentIndex < src.end; parentIndex++) {
79
+ const path: UpPath = {
80
+ parent,
81
+ parentField,
82
+ parentIndex,
83
+ };
84
+ // The only way a detached node can be re-attached (and become visible/usable again) is via a move, so mark it as moved ahead of time.
85
+ const node = getNodeAtPath(forest, path);
86
+ if (node !== undefined && dirty.get(node) === undefined) {
87
+ // Only mark the node as moved if it is not already marked as something else.
88
+ dirty.set(node, "moved");
89
+ }
90
+ // Mark the parent as changed unless it is already marked as new.
91
+ const parentNode = getNodeAtPath(forest, parent);
92
+ if (parentNode !== undefined && dirty.get(parentNode) !== "new") {
93
+ dirty.set(parentNode, "changed");
94
+ }
95
+ }
96
+ },
97
+ afterAttach: (_, dst) => {
98
+ assert(parent !== undefined, "Expected node");
99
+ assert(parentField !== undefined, "Expected field");
100
+ for (let parentIndex = dst.start; parentIndex < dst.end; parentIndex++) {
101
+ const path: UpPath = {
102
+ parent,
103
+ parentField,
104
+ parentIndex,
105
+ };
106
+ const node = getNodeAtPath(forest, path);
107
+ if (node !== undefined && dirty.get(node) === undefined) {
108
+ // Only mark the node as new if it is not already marked as something else - this ensures that a moved node is not marked as new (since nodes are marked move when detached).
109
+ dirty.set(node, "new");
110
+ }
111
+ // Mark the parent as changed unless it is already marked as new.
112
+ const parentNode = getNodeAtPath(forest, parent);
113
+ if (parentNode !== undefined && dirty.get(parentNode) !== "new") {
114
+ dirty.set(parentNode, "changed");
115
+ }
116
+ }
117
+ },
118
+ enterNode(index: number): void {
119
+ assert(parentField !== undefined, "Expected field");
120
+ parent = {
121
+ parent,
122
+ parentField,
123
+ parentIndex: index,
124
+ };
125
+ parentField = undefined;
126
+ },
127
+ exitNode(): void {
128
+ assert(parent !== undefined, "Expected node");
129
+ parentField = parent.parentField;
130
+ parent = parent.parent;
131
+ },
132
+ enterField: (key: FieldKey) => {
133
+ parentField = key;
134
+ },
135
+ exitField(): void {
136
+ parentField = undefined;
137
+ },
138
+ });
139
+ }
140
+
141
+ function getNodeAtPath(forest: IEditableForest, path: UpPath): TreeNode | undefined {
142
+ const cursor = forest.allocateCursor();
143
+ forest.moveCursorToPath(path, cursor);
144
+ assert(cursor.mode === CursorLocationType.Nodes, 0xa9c /* attach should happen in a node */);
145
+ const anchor = cursor.buildAnchor();
146
+ const anchorNode = forest.anchors.locate(anchor);
147
+ cursor.free();
148
+ if (anchorNode !== undefined) {
149
+ const node = treeNodeFromAnchor(anchorNode);
150
+ if (node instanceof TreeNode) {
151
+ return node;
152
+ }
153
+ }
154
+ return undefined;
155
+ }
@@ -56,6 +56,11 @@ export {
56
56
  tryGetSchema,
57
57
  } from "./treeNodeApi.js";
58
58
  export { createFromCursor } from "./create.js";
59
+ export {
60
+ type DirtyTreeStatus,
61
+ trackDirtyNodes,
62
+ type DirtyTreeMap,
63
+ } from "./dirtyIndex.js";
59
64
  export {
60
65
  type JsonSchemaId,
61
66
  type JsonSchemaType,
@@ -257,11 +257,12 @@ export interface TreeBranch extends IDisposable {
257
257
  /**
258
258
  * An editable view of a (version control style) branch of a shared tree based on some schema.
259
259
  *
260
- * This schema--known as the view schema--may or may not align the stored schema of the document.
260
+ * @remarks
261
+ * This schema (known as the view schema) may or may not align with the stored schema of the document.
261
262
  * Information about discrepancies between the two schemas is available via {@link TreeView.compatibility | compatibility}.
262
263
  *
263
- * Application authors are encouraged to read [schema-evolution.md](../../docs/user-facing/schema-evolution.md) and
264
- * choose a schema compatibility policy that aligns with their application's needs.
264
+ * Application authors are encouraged to read {@link https://github.com/microsoft/FluidFramework/blob/main/packages/dds/tree/docs/user-facing/schema-evolution.md | schema-evolution.md}
265
+ * and choose a schema compatibility policy that aligns with their application's needs.
265
266
  *
266
267
  * @privateRemarks
267
268
  * From an API design perspective, `upgradeSchema` could be merged into `viewWith` and/or `viewWith` could return errors explicitly on incompatible documents.
@@ -50,7 +50,6 @@ import {
50
50
  type HydratedFlexTreeNode,
51
51
  cursorForMapTreeField,
52
52
  type MinimalFieldMap,
53
- currentObserver,
54
53
  } from "../../feature-libraries/index.js";
55
54
  import { brand, filterIterable, getOrCreate, mapIterable } from "../../util/index.js";
56
55
 
@@ -145,10 +144,8 @@ export class UnhydratedFlexTreeNode
145
144
  */
146
145
  public readonly fields: MinimalFieldMap<UnhydratedFlexTreeField> = {
147
146
  get: (key: FieldKey): UnhydratedFlexTreeField | undefined => this.tryGetField(key),
148
- [Symbol.iterator]: (): IterableIterator<[FieldKey, UnhydratedFlexTreeField]> => {
149
- currentObserver?.observeNodeFields(this);
150
- return filterIterable(this.fieldsAll, ([, field]) => field.length > 0);
151
- },
147
+ [Symbol.iterator]: (): IterableIterator<[FieldKey, UnhydratedFlexTreeField]> =>
148
+ filterIterable(this.fieldsAll, ([, field]) => field.length > 0),
152
149
  };
153
150
 
154
151
  public [Symbol.iterator](): IterableIterator<UnhydratedFlexTreeField> {
@@ -221,7 +218,6 @@ export class UnhydratedFlexTreeNode
221
218
  * @remarks If this node is unparented, this method will return the special {@link unparentedLocation} as the parent.
222
219
  */
223
220
  public get parentField(): LocationInField {
224
- currentObserver?.observeParentOf(this);
225
221
  return this.location;
226
222
  }
227
223
 
@@ -230,8 +226,6 @@ export class UnhydratedFlexTreeNode
230
226
  }
231
227
 
232
228
  public tryGetField(key: FieldKey): UnhydratedFlexTreeField | undefined {
233
- currentObserver?.observeNodeField(this, key);
234
-
235
229
  const field = this.fieldsAll.get(key);
236
230
  // Only return the field if it is not empty, in order to fulfill the contract of `tryGetField`.
237
231
  if (field !== undefined && field.length > 0) {
@@ -241,9 +235,6 @@ export class UnhydratedFlexTreeNode
241
235
 
242
236
  public getBoxed(key: string): UnhydratedFlexTreeField {
243
237
  const fieldKey: FieldKey = brand(key);
244
-
245
- currentObserver?.observeNodeField(this, fieldKey);
246
-
247
238
  return this.getOrCreateField(fieldKey);
248
239
  }
249
240
 
@@ -89,6 +89,9 @@ export {
89
89
  type IdentifierIndex,
90
90
  createSimpleTreeIndex,
91
91
  createIdentifierIndex,
92
+ type DirtyTreeStatus,
93
+ trackDirtyNodes,
94
+ type DirtyTreeMap,
92
95
  type JsonSchemaId,
93
96
  type JsonSchemaType,
94
97
  type JsonObjectNodeSchema,
@@ -1,27 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import type { FlexTreeNode } from "./flexTreeTypes.js";
6
- import type { FieldKey } from "../../core/index.js";
7
- /**
8
- * An object informed about observation made to trees.
9
- * @remarks
10
- * See {@link withObservation} and {@link currentObserver}.
11
- */
12
- export interface Observer {
13
- observeNodeFields(node: FlexTreeNode): void;
14
- observeNodeField(node: FlexTreeNode, key: FieldKey): void;
15
- observeParentOf(node: FlexTreeNode): void;
16
- }
17
- /**
18
- * The current observer, if any.
19
- * @remarks
20
- * Set via {@link setObserver} as used by {@link withObservation}.
21
- */
22
- export declare let currentObserver: Observer | undefined;
23
- /**
24
- * For the duration of `f`, pushes `newObserver` onto the observer stack, making it the {@link currentObserver}.
25
- */
26
- export declare function withObservation<T>(newObserver: Observer | undefined, f: () => T): T;
27
- //# sourceMappingURL=observer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"observer.d.ts","sourceRoot":"","sources":["../../../src/feature-libraries/flex-tree/observer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AASpD;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACxB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5C,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1D,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;CAC1C;AACD;;;;GAIG;AACH,eAAO,IAAI,eAAe,EAAE,QAAQ,GAAG,SAAS,CAAC;AAgBjD;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAOnF"}
@@ -1,33 +0,0 @@
1
- "use strict";
2
- /*!
3
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
- * Licensed under the MIT License.
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.withObservation = exports.currentObserver = void 0;
8
- const internal_1 = require("@fluidframework/core-utils/internal");
9
- const observerStack = [];
10
- function setObserver(newObserver) {
11
- observerStack.push(newObserver);
12
- exports.currentObserver = newObserver;
13
- }
14
- function clearObserver() {
15
- (0, internal_1.debugAssert)(() => observerStack.length > 0 || "Empty Observer stack on clear");
16
- const popped = observerStack.pop();
17
- (0, internal_1.debugAssert)(() => popped === exports.currentObserver || "Mismatched observer stack");
18
- exports.currentObserver = observerStack[observerStack.length - 1];
19
- }
20
- /**
21
- * For the duration of `f`, pushes `newObserver` onto the observer stack, making it the {@link currentObserver}.
22
- */
23
- function withObservation(newObserver, f) {
24
- setObserver(newObserver);
25
- try {
26
- return f();
27
- }
28
- finally {
29
- clearObserver();
30
- }
31
- }
32
- exports.withObservation = withObservation;
33
- //# sourceMappingURL=observer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"observer.js","sourceRoot":"","sources":["../../../src/feature-libraries/flex-tree/observer.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,kEAAkE;AA4BlE,MAAM,aAAa,GAA6B,EAAE,CAAC;AAEnD,SAAS,WAAW,CAAC,WAAiC;IACrD,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAChC,uBAAe,GAAG,WAAW,CAAC;AAC/B,CAAC;AAED,SAAS,aAAa;IACrB,IAAA,sBAAW,EAAC,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,IAAI,+BAA+B,CAAC,CAAC;IAC/E,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,EAAE,CAAC;IACnC,IAAA,sBAAW,EAAC,GAAG,EAAE,CAAC,MAAM,KAAK,uBAAe,IAAI,2BAA2B,CAAC,CAAC;IAC7E,uBAAe,GAAG,aAAa,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe,CAAI,WAAiC,EAAE,CAAU;IAC/E,WAAW,CAAC,WAAW,CAAC,CAAC;IACzB,IAAI,CAAC;QACJ,OAAO,CAAC,EAAE,CAAC;IACZ,CAAC;YAAS,CAAC;QACV,aAAa,EAAE,CAAC;IACjB,CAAC;AACF,CAAC;AAPD,0CAOC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { debugAssert } from \"@fluidframework/core-utils/internal\";\nimport type { FlexTreeNode } from \"./flexTreeTypes.js\";\nimport type { FieldKey } from \"../../core/index.js\";\n\n/*\n * This file sets up a static observation tracking system.\n *\n * This library used to contain a more general variant of this which was deleted in https://github.com/microsoft/FluidFramework/pull/18659.\n * This pattern somewhat resembles the approach in https://github.com/tc39/proposal-signals.\n */\n\n/**\n * An object informed about observation made to trees.\n * @remarks\n * See {@link withObservation} and {@link currentObserver}.\n */\nexport interface Observer {\n\tobserveNodeFields(node: FlexTreeNode): void;\n\tobserveNodeField(node: FlexTreeNode, key: FieldKey): void;\n\tobserveParentOf(node: FlexTreeNode): void;\n}\n/**\n * The current observer, if any.\n * @remarks\n * Set via {@link setObserver} as used by {@link withObservation}.\n */\nexport let currentObserver: Observer | undefined;\n\nconst observerStack: (Observer | undefined)[] = [];\n\nfunction setObserver(newObserver: Observer | undefined): void {\n\tobserverStack.push(newObserver);\n\tcurrentObserver = newObserver;\n}\n\nfunction clearObserver(): void {\n\tdebugAssert(() => observerStack.length > 0 || \"Empty Observer stack on clear\");\n\tconst popped = observerStack.pop();\n\tdebugAssert(() => popped === currentObserver || \"Mismatched observer stack\");\n\tcurrentObserver = observerStack[observerStack.length - 1];\n}\n\n/**\n * For the duration of `f`, pushes `newObserver` onto the observer stack, making it the {@link currentObserver}.\n */\nexport function withObservation<T>(newObserver: Observer | undefined, f: () => T): T {\n\tsetObserver(newObserver);\n\ttry {\n\t\treturn f();\n\t} finally {\n\t\tclearObserver();\n\t}\n}\n"]}
@@ -1,27 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import type { FlexTreeNode } from "./flexTreeTypes.js";
6
- import type { FieldKey } from "../../core/index.js";
7
- /**
8
- * An object informed about observation made to trees.
9
- * @remarks
10
- * See {@link withObservation} and {@link currentObserver}.
11
- */
12
- export interface Observer {
13
- observeNodeFields(node: FlexTreeNode): void;
14
- observeNodeField(node: FlexTreeNode, key: FieldKey): void;
15
- observeParentOf(node: FlexTreeNode): void;
16
- }
17
- /**
18
- * The current observer, if any.
19
- * @remarks
20
- * Set via {@link setObserver} as used by {@link withObservation}.
21
- */
22
- export declare let currentObserver: Observer | undefined;
23
- /**
24
- * For the duration of `f`, pushes `newObserver` onto the observer stack, making it the {@link currentObserver}.
25
- */
26
- export declare function withObservation<T>(newObserver: Observer | undefined, f: () => T): T;
27
- //# sourceMappingURL=observer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"observer.d.ts","sourceRoot":"","sources":["../../../src/feature-libraries/flex-tree/observer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AASpD;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACxB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5C,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1D,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;CAC1C;AACD;;;;GAIG;AACH,eAAO,IAAI,eAAe,EAAE,QAAQ,GAAG,SAAS,CAAC;AAgBjD;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAOnF"}
@@ -1,35 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import { debugAssert } from "@fluidframework/core-utils/internal";
6
- /**
7
- * The current observer, if any.
8
- * @remarks
9
- * Set via {@link setObserver} as used by {@link withObservation}.
10
- */
11
- export let currentObserver;
12
- const observerStack = [];
13
- function setObserver(newObserver) {
14
- observerStack.push(newObserver);
15
- currentObserver = newObserver;
16
- }
17
- function clearObserver() {
18
- debugAssert(() => observerStack.length > 0 || "Empty Observer stack on clear");
19
- const popped = observerStack.pop();
20
- debugAssert(() => popped === currentObserver || "Mismatched observer stack");
21
- currentObserver = observerStack[observerStack.length - 1];
22
- }
23
- /**
24
- * For the duration of `f`, pushes `newObserver` onto the observer stack, making it the {@link currentObserver}.
25
- */
26
- export function withObservation(newObserver, f) {
27
- setObserver(newObserver);
28
- try {
29
- return f();
30
- }
31
- finally {
32
- clearObserver();
33
- }
34
- }
35
- //# sourceMappingURL=observer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"observer.js","sourceRoot":"","sources":["../../../src/feature-libraries/flex-tree/observer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAqBlE;;;;GAIG;AACH,MAAM,CAAC,IAAI,eAAqC,CAAC;AAEjD,MAAM,aAAa,GAA6B,EAAE,CAAC;AAEnD,SAAS,WAAW,CAAC,WAAiC;IACrD,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAChC,eAAe,GAAG,WAAW,CAAC;AAC/B,CAAC;AAED,SAAS,aAAa;IACrB,WAAW,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,IAAI,+BAA+B,CAAC,CAAC;IAC/E,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,EAAE,CAAC;IACnC,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,KAAK,eAAe,IAAI,2BAA2B,CAAC,CAAC;IAC7E,eAAe,GAAG,aAAa,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAI,WAAiC,EAAE,CAAU;IAC/E,WAAW,CAAC,WAAW,CAAC,CAAC;IACzB,IAAI,CAAC;QACJ,OAAO,CAAC,EAAE,CAAC;IACZ,CAAC;YAAS,CAAC;QACV,aAAa,EAAE,CAAC;IACjB,CAAC;AACF,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { debugAssert } from \"@fluidframework/core-utils/internal\";\nimport type { FlexTreeNode } from \"./flexTreeTypes.js\";\nimport type { FieldKey } from \"../../core/index.js\";\n\n/*\n * This file sets up a static observation tracking system.\n *\n * This library used to contain a more general variant of this which was deleted in https://github.com/microsoft/FluidFramework/pull/18659.\n * This pattern somewhat resembles the approach in https://github.com/tc39/proposal-signals.\n */\n\n/**\n * An object informed about observation made to trees.\n * @remarks\n * See {@link withObservation} and {@link currentObserver}.\n */\nexport interface Observer {\n\tobserveNodeFields(node: FlexTreeNode): void;\n\tobserveNodeField(node: FlexTreeNode, key: FieldKey): void;\n\tobserveParentOf(node: FlexTreeNode): void;\n}\n/**\n * The current observer, if any.\n * @remarks\n * Set via {@link setObserver} as used by {@link withObservation}.\n */\nexport let currentObserver: Observer | undefined;\n\nconst observerStack: (Observer | undefined)[] = [];\n\nfunction setObserver(newObserver: Observer | undefined): void {\n\tobserverStack.push(newObserver);\n\tcurrentObserver = newObserver;\n}\n\nfunction clearObserver(): void {\n\tdebugAssert(() => observerStack.length > 0 || \"Empty Observer stack on clear\");\n\tconst popped = observerStack.pop();\n\tdebugAssert(() => popped === currentObserver || \"Mismatched observer stack\");\n\tcurrentObserver = observerStack[observerStack.length - 1];\n}\n\n/**\n * For the duration of `f`, pushes `newObserver` onto the observer stack, making it the {@link currentObserver}.\n */\nexport function withObservation<T>(newObserver: Observer | undefined, f: () => T): T {\n\tsetObserver(newObserver);\n\ttry {\n\t\treturn f();\n\t} finally {\n\t\tclearObserver();\n\t}\n}\n"]}