@fluidframework/tree 2.61.0-355934 → 2.61.0-356132
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.
- package/api-report/tree.alpha.api.md +8 -0
- package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +2 -0
- package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
- package/dist/feature-libraries/flex-tree/index.d.ts +1 -0
- package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/index.js +4 -1
- package/dist/feature-libraries/flex-tree/index.js.map +1 -1
- package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/lazyNode.js +15 -8
- package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
- package/dist/feature-libraries/flex-tree/observer.d.ts +27 -0
- package/dist/feature-libraries/flex-tree/observer.d.ts.map +1 -0
- package/dist/feature-libraries/flex-tree/observer.js +33 -0
- package/dist/feature-libraries/flex-tree/observer.js.map +1 -0
- package/dist/feature-libraries/index.d.ts +1 -1
- package/dist/feature-libraries/index.d.ts.map +1 -1
- package/dist/feature-libraries/index.js +3 -1
- package/dist/feature-libraries/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/shared-tree/treeAlpha.d.ts +28 -1
- package/dist/shared-tree/treeAlpha.d.ts.map +1 -1
- package/dist/shared-tree/treeAlpha.js +139 -0
- package/dist/shared-tree/treeAlpha.js.map +1 -1
- package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
- package/dist/simple-tree/core/unhydratedFlexTree.js +7 -1
- package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
- package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +2 -0
- package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
- package/lib/feature-libraries/flex-tree/index.d.ts +1 -0
- package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/index.js +1 -0
- package/lib/feature-libraries/flex-tree/index.js.map +1 -1
- package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/lazyNode.js +15 -8
- package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
- package/lib/feature-libraries/flex-tree/observer.d.ts +27 -0
- package/lib/feature-libraries/flex-tree/observer.d.ts.map +1 -0
- package/lib/feature-libraries/flex-tree/observer.js +35 -0
- package/lib/feature-libraries/flex-tree/observer.js.map +1 -0
- package/lib/feature-libraries/index.d.ts +1 -1
- package/lib/feature-libraries/index.d.ts.map +1 -1
- package/lib/feature-libraries/index.js +1 -1
- package/lib/feature-libraries/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/shared-tree/treeAlpha.d.ts +28 -1
- package/lib/shared-tree/treeAlpha.d.ts.map +1 -1
- package/lib/shared-tree/treeAlpha.js +141 -2
- package/lib/shared-tree/treeAlpha.js.map +1 -1
- package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
- package/lib/simple-tree/core/unhydratedFlexTree.js +8 -2
- package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
- package/package.json +21 -21
- package/src/feature-libraries/flex-tree/flexTreeTypes.ts +2 -0
- package/src/feature-libraries/flex-tree/index.ts +2 -0
- package/src/feature-libraries/flex-tree/lazyNode.ts +13 -3
- package/src/feature-libraries/flex-tree/observer.ts +58 -0
- package/src/feature-libraries/index.ts +3 -0
- package/src/packageVersion.ts +1 -1
- package/src/shared-tree/treeAlpha.ts +213 -2
- package/src/simple-tree/core/unhydratedFlexTree.ts +11 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/tree",
|
|
3
|
-
"version": "2.61.0-
|
|
3
|
+
"version": "2.61.0-356132",
|
|
4
4
|
"description": "Distributed tree",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -101,17 +101,17 @@
|
|
|
101
101
|
"temp-directory": "nyc/.nyc_output"
|
|
102
102
|
},
|
|
103
103
|
"dependencies": {
|
|
104
|
-
"@fluid-internal/client-utils": "2.61.0-
|
|
105
|
-
"@fluidframework/container-runtime": "2.61.0-
|
|
106
|
-
"@fluidframework/core-interfaces": "2.61.0-
|
|
107
|
-
"@fluidframework/core-utils": "2.61.0-
|
|
108
|
-
"@fluidframework/datastore-definitions": "2.61.0-
|
|
109
|
-
"@fluidframework/driver-definitions": "2.61.0-
|
|
110
|
-
"@fluidframework/id-compressor": "2.61.0-
|
|
111
|
-
"@fluidframework/runtime-definitions": "2.61.0-
|
|
112
|
-
"@fluidframework/runtime-utils": "2.61.0-
|
|
113
|
-
"@fluidframework/shared-object-base": "2.61.0-
|
|
114
|
-
"@fluidframework/telemetry-utils": "2.61.0-
|
|
104
|
+
"@fluid-internal/client-utils": "2.61.0-356132",
|
|
105
|
+
"@fluidframework/container-runtime": "2.61.0-356132",
|
|
106
|
+
"@fluidframework/core-interfaces": "2.61.0-356132",
|
|
107
|
+
"@fluidframework/core-utils": "2.61.0-356132",
|
|
108
|
+
"@fluidframework/datastore-definitions": "2.61.0-356132",
|
|
109
|
+
"@fluidframework/driver-definitions": "2.61.0-356132",
|
|
110
|
+
"@fluidframework/id-compressor": "2.61.0-356132",
|
|
111
|
+
"@fluidframework/runtime-definitions": "2.61.0-356132",
|
|
112
|
+
"@fluidframework/runtime-utils": "2.61.0-356132",
|
|
113
|
+
"@fluidframework/shared-object-base": "2.61.0-356132",
|
|
114
|
+
"@fluidframework/telemetry-utils": "2.61.0-356132",
|
|
115
115
|
"@sinclair/typebox": "^0.34.13",
|
|
116
116
|
"@tylerbu/sorted-btree-es6": "^1.8.0",
|
|
117
117
|
"@types/ungap__structured-clone": "^1.2.0",
|
|
@@ -121,19 +121,19 @@
|
|
|
121
121
|
"devDependencies": {
|
|
122
122
|
"@arethetypeswrong/cli": "^0.17.1",
|
|
123
123
|
"@biomejs/biome": "~1.9.3",
|
|
124
|
-
"@fluid-internal/mocha-test-setup": "2.61.0-
|
|
125
|
-
"@fluid-private/stochastic-test-utils": "2.61.0-
|
|
126
|
-
"@fluid-private/test-dds-utils": "2.61.0-
|
|
127
|
-
"@fluid-private/test-drivers": "2.61.0-
|
|
124
|
+
"@fluid-internal/mocha-test-setup": "2.61.0-356132",
|
|
125
|
+
"@fluid-private/stochastic-test-utils": "2.61.0-356132",
|
|
126
|
+
"@fluid-private/test-dds-utils": "2.61.0-356132",
|
|
127
|
+
"@fluid-private/test-drivers": "2.61.0-356132",
|
|
128
128
|
"@fluid-tools/benchmark": "^0.51.0",
|
|
129
129
|
"@fluid-tools/build-cli": "^0.58.3",
|
|
130
130
|
"@fluidframework/build-common": "^2.0.3",
|
|
131
131
|
"@fluidframework/build-tools": "^0.58.3",
|
|
132
|
-
"@fluidframework/container-definitions": "2.61.0-
|
|
133
|
-
"@fluidframework/container-loader": "2.61.0-
|
|
132
|
+
"@fluidframework/container-definitions": "2.61.0-356132",
|
|
133
|
+
"@fluidframework/container-loader": "2.61.0-356132",
|
|
134
134
|
"@fluidframework/eslint-config-fluid": "^6.0.0",
|
|
135
|
-
"@fluidframework/test-runtime-utils": "2.61.0-
|
|
136
|
-
"@fluidframework/test-utils": "2.61.0-
|
|
135
|
+
"@fluidframework/test-runtime-utils": "2.61.0-356132",
|
|
136
|
+
"@fluidframework/test-utils": "2.61.0-356132",
|
|
137
137
|
"@fluidframework/tree-previous": "npm:@fluidframework/tree@2.60.0",
|
|
138
138
|
"@microsoft/api-extractor": "7.52.11",
|
|
139
139
|
"@types/diff": "^3.5.1",
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
"test:customBenchmarks:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:customBenchmarks",
|
|
228
228
|
"test:memory": "mocha --perfMode --config ./src/test/memory/.mocharc.cjs",
|
|
229
229
|
"test:memory-profiling:report": "mocha --config ./src/test/memory/.mocharc.cjs",
|
|
230
|
-
"test:mocha": "npm run test:mocha:
|
|
230
|
+
"test:mocha": "echo skipping non-smoke cjs to avoid overhead && npm run test:mocha:cjs -- --fgrep @Smoke && npm run test:mocha:esm",
|
|
231
231
|
"test:mocha:cjs": "cross-env MOCHA_SPEC=dist/test mocha",
|
|
232
232
|
"test:mocha:esm": "mocha",
|
|
233
233
|
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
@@ -151,6 +151,8 @@ export enum TreeStatus {
|
|
|
151
151
|
*
|
|
152
152
|
* @remarks
|
|
153
153
|
* All editing is actually done via {@link FlexTreeField}s: the nodes are immutable other than that they contain mutable fields.
|
|
154
|
+
*
|
|
155
|
+
* All implementations should track read access in {@link currentObserver}'s observation methods as appropriate.
|
|
154
156
|
*/
|
|
155
157
|
export interface FlexTreeNode extends FlexTreeEntity, MapTreeNodeViewGeneric<FlexTreeNode> {
|
|
156
158
|
readonly [flexTreeMarker]: FlexTreeEntityKind.Node;
|
|
@@ -42,3 +42,5 @@ export { getOrCreateHydratedFlexTreeNode } from "./lazyNode.js";
|
|
|
42
42
|
export { getSchemaAndPolicy, indexForAt } from "./utilities.js";
|
|
43
43
|
|
|
44
44
|
export { treeStatusFromAnchorCache } from "./utilities.js";
|
|
45
|
+
|
|
46
|
+
export { currentObserver, withObservation, type Observer } from "./observer.js";
|
|
@@ -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
|
-
|
|
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,58 @@
|
|
|
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
|
+
* The current observer, if any.
|
|
29
|
+
* @remarks
|
|
30
|
+
* Set via {@link setObserver} as used by {@link withObservation}.
|
|
31
|
+
*/
|
|
32
|
+
export let currentObserver: Observer | undefined;
|
|
33
|
+
|
|
34
|
+
const observerStack: (Observer | undefined)[] = [];
|
|
35
|
+
|
|
36
|
+
function setObserver(newObserver: Observer | undefined): void {
|
|
37
|
+
observerStack.push(newObserver);
|
|
38
|
+
currentObserver = newObserver;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function clearObserver(): void {
|
|
42
|
+
debugAssert(() => observerStack.length > 0 || "Empty Observer stack on clear");
|
|
43
|
+
const popped = observerStack.pop();
|
|
44
|
+
debugAssert(() => popped === currentObserver || "Mismatched observer stack");
|
|
45
|
+
currentObserver = observerStack[observerStack.length - 1];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* For the duration of `f`, pushes `newObserver` onto the observer stack, making it the {@link currentObserver}.
|
|
50
|
+
*/
|
|
51
|
+
export function withObservation<T>(newObserver: Observer | undefined, f: () => T): T {
|
|
52
|
+
setObserver(newObserver);
|
|
53
|
+
try {
|
|
54
|
+
return f();
|
|
55
|
+
} finally {
|
|
56
|
+
clearObserver();
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { IIdCompressor } from "@fluidframework/id-compressor";
|
|
|
17
17
|
import {
|
|
18
18
|
asIndex,
|
|
19
19
|
getKernel,
|
|
20
|
-
|
|
20
|
+
TreeNode,
|
|
21
21
|
type Unhydrated,
|
|
22
22
|
TreeBeta,
|
|
23
23
|
tryGetSchema,
|
|
@@ -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,
|
|
@@ -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,189 @@ 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
|
+
* 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
|
+
};
|
|
422
609
|
}
|
|
423
610
|
|
|
424
611
|
/**
|
|
@@ -427,6 +614,30 @@ export interface TreeAlpha {
|
|
|
427
614
|
* @alpha
|
|
428
615
|
*/
|
|
429
616
|
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
|
+
|
|
430
641
|
branch(node: TreeNode): TreeBranch | undefined {
|
|
431
642
|
const kernel = getKernel(node);
|
|
432
643
|
if (!kernel.isHydrated()) {
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
type HydratedFlexTreeNode,
|
|
51
51
|
cursorForMapTreeField,
|
|
52
52
|
type MinimalFieldMap,
|
|
53
|
+
currentObserver,
|
|
53
54
|
} from "../../feature-libraries/index.js";
|
|
54
55
|
import { brand, filterIterable, getOrCreate, mapIterable } from "../../util/index.js";
|
|
55
56
|
|
|
@@ -144,8 +145,10 @@ export class UnhydratedFlexTreeNode
|
|
|
144
145
|
*/
|
|
145
146
|
public readonly fields: MinimalFieldMap<UnhydratedFlexTreeField> = {
|
|
146
147
|
get: (key: FieldKey): UnhydratedFlexTreeField | undefined => this.tryGetField(key),
|
|
147
|
-
[Symbol.iterator]: (): IterableIterator<[FieldKey, UnhydratedFlexTreeField]> =>
|
|
148
|
-
|
|
148
|
+
[Symbol.iterator]: (): IterableIterator<[FieldKey, UnhydratedFlexTreeField]> => {
|
|
149
|
+
currentObserver?.observeNodeFields(this);
|
|
150
|
+
return filterIterable(this.fieldsAll, ([, field]) => field.length > 0);
|
|
151
|
+
},
|
|
149
152
|
};
|
|
150
153
|
|
|
151
154
|
public [Symbol.iterator](): IterableIterator<UnhydratedFlexTreeField> {
|
|
@@ -218,6 +221,7 @@ export class UnhydratedFlexTreeNode
|
|
|
218
221
|
* @remarks If this node is unparented, this method will return the special {@link unparentedLocation} as the parent.
|
|
219
222
|
*/
|
|
220
223
|
public get parentField(): LocationInField {
|
|
224
|
+
currentObserver?.observeParentOf(this);
|
|
221
225
|
return this.location;
|
|
222
226
|
}
|
|
223
227
|
|
|
@@ -226,6 +230,8 @@ export class UnhydratedFlexTreeNode
|
|
|
226
230
|
}
|
|
227
231
|
|
|
228
232
|
public tryGetField(key: FieldKey): UnhydratedFlexTreeField | undefined {
|
|
233
|
+
currentObserver?.observeNodeField(this, key);
|
|
234
|
+
|
|
229
235
|
const field = this.fieldsAll.get(key);
|
|
230
236
|
// Only return the field if it is not empty, in order to fulfill the contract of `tryGetField`.
|
|
231
237
|
if (field !== undefined && field.length > 0) {
|
|
@@ -235,6 +241,9 @@ export class UnhydratedFlexTreeNode
|
|
|
235
241
|
|
|
236
242
|
public getBoxed(key: string): UnhydratedFlexTreeField {
|
|
237
243
|
const fieldKey: FieldKey = brand(key);
|
|
244
|
+
|
|
245
|
+
currentObserver?.observeNodeField(this, fieldKey);
|
|
246
|
+
|
|
238
247
|
return this.getOrCreateField(fieldKey);
|
|
239
248
|
}
|
|
240
249
|
|