@fluidframework/tree 2.10.0-306579 → 2.10.0-307399
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 +11 -9
- package/api-report/tree.beta.api.md +2 -0
- package/api-report/tree.legacy.alpha.api.md +2 -0
- package/api-report/tree.legacy.public.api.md +2 -0
- package/api-report/tree.public.api.md +2 -0
- package/dist/feature-libraries/chunked-forest/basicChunk.d.ts +26 -5
- package/dist/feature-libraries/chunked-forest/basicChunk.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/basicChunk.js +15 -5
- package/dist/feature-libraries/chunked-forest/basicChunk.js.map +1 -1
- package/dist/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
- package/dist/feature-libraries/chunked-forest/chunkedForest.js +5 -0
- package/dist/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
- 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 +2 -2
- package/dist/feature-libraries/index.js.map +1 -1
- package/dist/feature-libraries/modular-schema/discrepancies.d.ts +27 -27
- package/dist/feature-libraries/modular-schema/discrepancies.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/discrepancies.js +152 -193
- package/dist/feature-libraries/modular-schema/discrepancies.js.map +1 -1
- package/dist/feature-libraries/modular-schema/index.d.ts +1 -1
- package/dist/feature-libraries/modular-schema/index.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/index.js +2 -2
- package/dist/feature-libraries/modular-schema/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/treeApi.js +4 -1
- package/dist/shared-tree/treeApi.js.map +1 -1
- package/dist/simple-tree/api/schemaCreationUtilities.d.ts +12 -14
- package/dist/simple-tree/api/schemaCreationUtilities.d.ts.map +1 -1
- package/dist/simple-tree/api/schemaCreationUtilities.js +9 -7
- package/dist/simple-tree/api/schemaCreationUtilities.js.map +1 -1
- package/dist/simple-tree/api/schemaFactory.d.ts +2 -0
- package/dist/simple-tree/api/schemaFactory.d.ts.map +1 -1
- package/dist/simple-tree/api/schemaFactory.js +4 -1
- package/dist/simple-tree/api/schemaFactory.js.map +1 -1
- package/dist/simple-tree/api/schemaFactoryRecursive.js.map +1 -1
- package/dist/simple-tree/core/treeNodeKernel.d.ts +4 -5
- package/dist/simple-tree/core/treeNodeKernel.d.ts.map +1 -1
- package/dist/simple-tree/core/treeNodeKernel.js +63 -67
- package/dist/simple-tree/core/treeNodeKernel.js.map +1 -1
- package/dist/simple-tree/objectNode.d.ts +1 -1
- package/dist/simple-tree/objectNode.js.map +1 -1
- package/dist/simple-tree/objectNodeTypes.d.ts +3 -0
- package/dist/simple-tree/objectNodeTypes.d.ts.map +1 -1
- package/dist/simple-tree/objectNodeTypes.js +3 -1
- package/dist/simple-tree/objectNodeTypes.js.map +1 -1
- package/docs/.attachments/object-merge-semantics.drawio +145 -0
- package/docs/user-facing/array-merge-semantics.md +344 -0
- package/docs/user-facing/map-merge-semantics.md +128 -0
- package/docs/user-facing/merge-semantics.md +7 -3
- package/docs/user-facing/object-merge-semantics.md +77 -0
- package/lib/feature-libraries/chunked-forest/basicChunk.d.ts +26 -5
- package/lib/feature-libraries/chunked-forest/basicChunk.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/basicChunk.js +15 -5
- package/lib/feature-libraries/chunked-forest/basicChunk.js.map +1 -1
- package/lib/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
- package/lib/feature-libraries/chunked-forest/chunkedForest.js +5 -0
- package/lib/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
- 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/feature-libraries/modular-schema/discrepancies.d.ts +27 -27
- package/lib/feature-libraries/modular-schema/discrepancies.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/discrepancies.js +150 -191
- package/lib/feature-libraries/modular-schema/discrepancies.js.map +1 -1
- package/lib/feature-libraries/modular-schema/index.d.ts +1 -1
- package/lib/feature-libraries/modular-schema/index.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/index.js +1 -1
- package/lib/feature-libraries/modular-schema/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/treeApi.js +5 -2
- package/lib/shared-tree/treeApi.js.map +1 -1
- package/lib/simple-tree/api/schemaCreationUtilities.d.ts +12 -14
- package/lib/simple-tree/api/schemaCreationUtilities.d.ts.map +1 -1
- package/lib/simple-tree/api/schemaCreationUtilities.js +9 -7
- package/lib/simple-tree/api/schemaCreationUtilities.js.map +1 -1
- package/lib/simple-tree/api/schemaFactory.d.ts +2 -0
- package/lib/simple-tree/api/schemaFactory.d.ts.map +1 -1
- package/lib/simple-tree/api/schemaFactory.js +4 -1
- package/lib/simple-tree/api/schemaFactory.js.map +1 -1
- package/lib/simple-tree/api/schemaFactoryRecursive.js.map +1 -1
- package/lib/simple-tree/core/treeNodeKernel.d.ts +4 -5
- package/lib/simple-tree/core/treeNodeKernel.d.ts.map +1 -1
- package/lib/simple-tree/core/treeNodeKernel.js +64 -68
- package/lib/simple-tree/core/treeNodeKernel.js.map +1 -1
- package/lib/simple-tree/objectNode.d.ts +1 -1
- package/lib/simple-tree/objectNode.js.map +1 -1
- package/lib/simple-tree/objectNodeTypes.d.ts +3 -0
- package/lib/simple-tree/objectNodeTypes.d.ts.map +1 -1
- package/lib/simple-tree/objectNodeTypes.js +3 -1
- package/lib/simple-tree/objectNodeTypes.js.map +1 -1
- package/package.json +23 -31
- package/src/feature-libraries/chunked-forest/basicChunk.ts +12 -4
- package/src/feature-libraries/chunked-forest/chunkedForest.ts +5 -0
- package/src/feature-libraries/index.ts +1 -1
- package/src/feature-libraries/modular-schema/discrepancies.ts +202 -241
- package/src/feature-libraries/modular-schema/index.ts +4 -1
- package/src/packageVersion.ts +1 -1
- package/src/shared-tree/treeApi.ts +7 -5
- package/src/simple-tree/api/schemaCreationUtilities.ts +29 -17
- package/src/simple-tree/api/schemaFactory.ts +25 -18
- package/src/simple-tree/api/schemaFactoryRecursive.ts +1 -1
- package/src/simple-tree/core/treeNodeKernel.ts +62 -64
- package/src/simple-tree/objectNode.ts +1 -1
- package/src/simple-tree/objectNodeTypes.ts +3 -1
|
@@ -73,8 +73,8 @@ export function singletonSchema<TScope extends string, TName extends string | nu
|
|
|
73
73
|
/**
|
|
74
74
|
* Converts an enum into a collection of schema which can be used in a union.
|
|
75
75
|
* @remarks
|
|
76
|
-
* Currently only supports `string` enums.
|
|
77
76
|
* The string value of the enum is used as the name of the schema: callers must ensure that it is stable and unique.
|
|
77
|
+
* Numeric enums values have the value implicitly converted into a string.
|
|
78
78
|
* Consider making a dedicated schema factory with a nested scope to avoid the enum members colliding with other schema.
|
|
79
79
|
* @example
|
|
80
80
|
* ```typescript
|
|
@@ -87,7 +87,7 @@ export function singletonSchema<TScope extends string, TName extends string | nu
|
|
|
87
87
|
* // Define the schema for each member of the enum using a nested scope to group them together.
|
|
88
88
|
* const ModeNodes = adaptEnum(new SchemaFactory(`${schemaFactory.scope}.Mode`), Mode);
|
|
89
89
|
* // Defined the types of the nodes which correspond to this the schema.
|
|
90
|
-
* type ModeNodes =
|
|
90
|
+
* type ModeNodes = TreeNodeFromImplicitAllowedTypes<(typeof ModeNodes.schema)>;
|
|
91
91
|
* // An example schema which has an enum as a child.
|
|
92
92
|
* class Parent extends schemaFactory.object("Parent", {
|
|
93
93
|
* // adaptEnum's return value has a ".schema" property can be use as an `AllowedTypes` array allowing any of the members of the enum.
|
|
@@ -106,8 +106,6 @@ export function singletonSchema<TScope extends string, TName extends string | nu
|
|
|
106
106
|
* }
|
|
107
107
|
* ```
|
|
108
108
|
* @privateRemarks
|
|
109
|
-
* TODO:
|
|
110
|
-
* Extend this to support numeric enums.
|
|
111
109
|
* Maybe provide `SchemaFactory.nested` to ease creating nested scopes?
|
|
112
110
|
* @see {@link enumFromStrings} for a similar function that works on arrays of strings instead of an enum.
|
|
113
111
|
* @alpha
|
|
@@ -139,9 +137,12 @@ export function adaptEnum<
|
|
|
139
137
|
|
|
140
138
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
141
139
|
const factoryOut = <TValue extends Values>(value: TValue) => {
|
|
142
|
-
return new out[
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
return new out[
|
|
141
|
+
inverse.get(value) ?? fail("missing enum value")
|
|
142
|
+
// "extends unknown" is required here to handle when TValue is an union: each member of the union should be processed independently.
|
|
143
|
+
]() as TValue extends unknown
|
|
144
|
+
? NodeFromSchema<ReturnType<typeof singletonSchema<TScope, TValue>>>
|
|
145
|
+
: never;
|
|
145
146
|
};
|
|
146
147
|
const out = factoryOut as typeof factoryOut & TOut & { readonly schema: SchemaArray };
|
|
147
148
|
for (const [key, value] of Object.entries(members)) {
|
|
@@ -176,7 +177,7 @@ export function adaptEnum<
|
|
|
176
177
|
* ```typescript
|
|
177
178
|
* const schemaFactory = new SchemaFactory("com.myApp");
|
|
178
179
|
* const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]);
|
|
179
|
-
* type Mode =
|
|
180
|
+
* type Mode = TreeNodeFromImplicitAllowedTypes<typeof Mode.schema>;
|
|
180
181
|
* const nodeFromString: Mode = Mode("Fun");
|
|
181
182
|
* const nodeFromSchema: Mode = new Mode.Fun();
|
|
182
183
|
*
|
|
@@ -198,21 +199,32 @@ export function enumFromStrings<
|
|
|
198
199
|
throw new UsageError("All members of enums must have distinct names");
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
type
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
type MembersUnion = Members[number];
|
|
203
|
+
|
|
204
|
+
// Get all keys of the Members tuple which are numeric strings as union of numbers:
|
|
205
|
+
type Indexes = Extract<keyof Members, `${number}`> extends `${infer N extends number}`
|
|
206
|
+
? N
|
|
207
|
+
: never;
|
|
208
|
+
|
|
209
|
+
type TOut = {
|
|
210
|
+
[Index in Indexes as Members[Index]]: ReturnType<
|
|
211
|
+
typeof singletonSchema<TScope, Members[Index] & string>
|
|
209
212
|
>;
|
|
210
213
|
};
|
|
211
214
|
|
|
212
|
-
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
216
|
+
const factoryOut = <TValue extends MembersUnion>(value: TValue) => {
|
|
217
|
+
// "extends unknown" is required here to handle when TValue is an union: each member of the union should be processed independently.
|
|
218
|
+
return new recordOut[value]() as TValue extends unknown
|
|
219
|
+
? NodeFromSchema<ReturnType<typeof singletonSchema<TScope, TValue>>>
|
|
220
|
+
: never;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
type SchemaArray = UnionToTuple<MembersUnion extends unknown ? TOut[MembersUnion] : never>;
|
|
213
224
|
const schemaArray: TreeNodeSchema[] = [];
|
|
214
225
|
|
|
215
226
|
const out = factoryOut as typeof factoryOut & TOut & { readonly schema: SchemaArray };
|
|
227
|
+
const recordOut = out as Record<MembersUnion, new () => unknown>;
|
|
216
228
|
for (const name of members) {
|
|
217
229
|
const schema = singletonSchema(factory, name);
|
|
218
230
|
schemaArray.push(schema);
|
|
@@ -804,6 +804,8 @@ export class SchemaFactory<
|
|
|
804
804
|
name,
|
|
805
805
|
allowedTypes as T & ImplicitAllowedTypes,
|
|
806
806
|
true,
|
|
807
|
+
// Setting this (implicitlyConstructable) to true seems to work ok currently, but not for other node kinds.
|
|
808
|
+
// Supporting this could be fragile and might break other future changes, so it's being kept as false for now.
|
|
807
809
|
false,
|
|
808
810
|
);
|
|
809
811
|
|
|
@@ -811,24 +813,29 @@ export class SchemaFactory<
|
|
|
811
813
|
ScopedSchemaName<TScope, Name>,
|
|
812
814
|
NodeKind.Map,
|
|
813
815
|
TreeMapNodeUnsafe<T> & WithType<ScopedSchemaName<TScope, Name>, NodeKind.Map>,
|
|
814
|
-
{
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
// Ideally this would be
|
|
831
|
-
//
|
|
816
|
+
| {
|
|
817
|
+
/**
|
|
818
|
+
* Iterator for the iterable of content for this node.
|
|
819
|
+
* @privateRemarks
|
|
820
|
+
* Wrapping the constructor parameter for recursive arrays and maps in an inlined object type avoids (for unknown reasons)
|
|
821
|
+
* the following compile error when declaring the recursive schema:
|
|
822
|
+
* `Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.`
|
|
823
|
+
* To benefit from this without impacting the API, the definition of `Iterable` has been inlined as such an object.
|
|
824
|
+
*
|
|
825
|
+
* If this workaround is kept, ideally this comment would be deduplicated with the other instance of it.
|
|
826
|
+
* Unfortunately attempts to do this failed to avoid the compile error this was introduced to solve.
|
|
827
|
+
*/
|
|
828
|
+
[Symbol.iterator](): Iterator<
|
|
829
|
+
[string, InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>]
|
|
830
|
+
>;
|
|
831
|
+
}
|
|
832
|
+
// Ideally this would be
|
|
833
|
+
// RestrictiveStringRecord<InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>>,
|
|
834
|
+
// but doing so breaks recursive types.
|
|
835
|
+
// Instead we do a less nice version:
|
|
836
|
+
| {
|
|
837
|
+
readonly [P in string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>;
|
|
838
|
+
},
|
|
832
839
|
false,
|
|
833
840
|
T,
|
|
834
841
|
undefined
|
|
@@ -140,7 +140,7 @@ export type ValidateRecursiveSchema<
|
|
|
140
140
|
? Iterable<[string, InsertableTreeNodeFromImplicitAllowedTypes<T["info"]>]>
|
|
141
141
|
: unknown;
|
|
142
142
|
}[T["kind"]],
|
|
143
|
-
// ImplicitlyConstructable: recursive types are not implicitly constructable.
|
|
143
|
+
// ImplicitlyConstructable: recursive types are currently not implicitly constructable.
|
|
144
144
|
false,
|
|
145
145
|
// Info: What's passed to the method to create the schema. Constraining these here should be about as effective as if the actual constraints existed on the actual method itself.
|
|
146
146
|
{
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
assertFlexTreeEntityNotFreed,
|
|
18
18
|
ContextSlot,
|
|
19
19
|
flexTreeSlot,
|
|
20
|
-
isFlexTreeNode,
|
|
21
20
|
isFreedSymbol,
|
|
22
21
|
LazyEntity,
|
|
23
22
|
TreeStatus,
|
|
@@ -71,10 +70,15 @@ export function tryGetTreeNodeSchema(value: unknown): undefined | TreeNodeSchema
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
/** The {@link HydrationState} of a {@link TreeNodeKernel} before the kernel is hydrated */
|
|
74
|
-
|
|
73
|
+
interface UnhydratedState {
|
|
74
|
+
off: Off;
|
|
75
|
+
innerNode: UnhydratedFlexTreeNode;
|
|
76
|
+
}
|
|
75
77
|
|
|
76
78
|
/** The {@link HydrationState} of a {@link TreeNodeKernel} after the kernel is hydrated */
|
|
77
79
|
interface HydratedState {
|
|
80
|
+
/** The flex node for this kernel (lazy - undefined if it has not yet been demanded) */
|
|
81
|
+
innerNode?: FlexTreeNode;
|
|
78
82
|
/** The {@link AnchorNode} that this node is associated with. */
|
|
79
83
|
anchorNode: AnchorNode;
|
|
80
84
|
/** All {@link Off | event deregistration functions} that should be run when the kernel is disposed. */
|
|
@@ -86,14 +90,13 @@ type HydrationState = UnhydratedState | HydratedState;
|
|
|
86
90
|
|
|
87
91
|
/** True if and only if the given {@link HydrationState} is post-hydration */
|
|
88
92
|
function isHydrated(state: HydrationState): state is HydratedState {
|
|
89
|
-
return
|
|
93
|
+
return (state as Partial<HydratedState>).anchorNode !== undefined;
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
/**
|
|
93
97
|
* Contains state and an internal API for managing {@link TreeNode}s.
|
|
94
98
|
* @remarks All {@link TreeNode}s have an associated kernel object.
|
|
95
99
|
* The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states.
|
|
96
|
-
* When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method.
|
|
97
100
|
*/
|
|
98
101
|
export class TreeNodeKernel {
|
|
99
102
|
private disposed = false;
|
|
@@ -110,7 +113,7 @@ export class TreeNodeKernel {
|
|
|
110
113
|
*/
|
|
111
114
|
public generationNumber: number = 0;
|
|
112
115
|
|
|
113
|
-
#hydrationState: HydrationState
|
|
116
|
+
#hydrationState: HydrationState;
|
|
114
117
|
|
|
115
118
|
/**
|
|
116
119
|
* Events registered before hydration.
|
|
@@ -133,7 +136,7 @@ export class TreeNodeKernel {
|
|
|
133
136
|
public constructor(
|
|
134
137
|
public readonly node: TreeNode,
|
|
135
138
|
public readonly schema: TreeNodeSchema,
|
|
136
|
-
|
|
139
|
+
innerNode: InnerNode,
|
|
137
140
|
private readonly initialContext: Context,
|
|
138
141
|
) {
|
|
139
142
|
assert(!treeNodeToKernel.has(node), 0xa1a /* only one kernel per node can be made */);
|
|
@@ -144,9 +147,9 @@ export class TreeNodeKernel {
|
|
|
144
147
|
mapTreeNodeToProxy.set(innerNode, node);
|
|
145
148
|
// Register for change events from the unhydrated flex node.
|
|
146
149
|
// These will be fired if the unhydrated node is edited, and will also be forwarded later to the hydrated node.
|
|
147
|
-
this.#hydrationState =
|
|
148
|
-
|
|
149
|
-
({ changedFields }) => {
|
|
150
|
+
this.#hydrationState = {
|
|
151
|
+
innerNode,
|
|
152
|
+
off: innerNode.events.on("childrenChangedAfterBatch", ({ changedFields }) => {
|
|
150
153
|
this.#unhydratedEvents.value.emit("childrenChangedAfterBatch", {
|
|
151
154
|
changedFields,
|
|
152
155
|
});
|
|
@@ -161,15 +164,16 @@ export class TreeNodeKernel {
|
|
|
161
164
|
// This cast is safe because the parent (if it exists) of an unhydrated flex node is always another unhydrated flex node.
|
|
162
165
|
n = n.parentField.parent.parent as UnhydratedFlexTreeNode | undefined;
|
|
163
166
|
}
|
|
164
|
-
},
|
|
165
|
-
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
166
169
|
} else {
|
|
167
170
|
// Hydrated case
|
|
171
|
+
const { anchorNode } = innerNode;
|
|
168
172
|
assert(
|
|
169
|
-
!
|
|
173
|
+
!anchorNode.slots.has(proxySlot),
|
|
170
174
|
0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */,
|
|
171
175
|
);
|
|
172
|
-
this.
|
|
176
|
+
this.#hydrationState = this.createHydratedState(anchorNode);
|
|
173
177
|
}
|
|
174
178
|
}
|
|
175
179
|
|
|
@@ -191,27 +195,11 @@ export class TreeNodeKernel {
|
|
|
191
195
|
* Happens at most once for any given node.
|
|
192
196
|
* Cleans up mappings to {@link UnhydratedFlexTreeNode} - it is assumed that they are no longer needed once the proxy has an anchor node.
|
|
193
197
|
*/
|
|
194
|
-
|
|
198
|
+
private hydrate(anchorNode: AnchorNode): void {
|
|
195
199
|
assert(!this.disposed, 0xa2a /* cannot hydrate a disposed node */);
|
|
196
200
|
assert(!isHydrated(this.#hydrationState), 0xa2b /* hydration should only happen once */);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (this.innerNode instanceof UnhydratedFlexTreeNode) {
|
|
200
|
-
mapTreeNodeToProxy.delete(this.innerNode);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced.
|
|
204
|
-
anchorNode.slots.set(proxySlot, this.node);
|
|
205
|
-
this.#hydrationState = {
|
|
206
|
-
anchorNode,
|
|
207
|
-
offAnchorNode: new Set([
|
|
208
|
-
anchorNode.events.on("afterDestroy", () => this.dispose()),
|
|
209
|
-
// TODO: this should be triggered on change even for unhydrated nodes.
|
|
210
|
-
anchorNode.events.on("childrenChanging", () => {
|
|
211
|
-
this.generationNumber += 1;
|
|
212
|
-
}),
|
|
213
|
-
]),
|
|
214
|
-
};
|
|
201
|
+
mapTreeNodeToProxy.delete(this.#hydrationState.innerNode);
|
|
202
|
+
this.#hydrationState = this.createHydratedState(anchorNode);
|
|
215
203
|
|
|
216
204
|
// If needed, register forwarding emitters for events from before hydration
|
|
217
205
|
if (this.#unhydratedEvents.evaluated) {
|
|
@@ -228,6 +216,20 @@ export class TreeNodeKernel {
|
|
|
228
216
|
}
|
|
229
217
|
}
|
|
230
218
|
|
|
219
|
+
private createHydratedState(anchorNode: AnchorNode): HydratedState {
|
|
220
|
+
anchorNode.slots.set(proxySlot, this.node);
|
|
221
|
+
return {
|
|
222
|
+
anchorNode,
|
|
223
|
+
offAnchorNode: new Set([
|
|
224
|
+
anchorNode.events.on("afterDestroy", () => this.dispose()),
|
|
225
|
+
// TODO: this should be triggered on change even for unhydrated nodes.
|
|
226
|
+
anchorNode.events.on("childrenChanging", () => {
|
|
227
|
+
this.generationNumber += 1;
|
|
228
|
+
}),
|
|
229
|
+
]),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
231
233
|
public getStatus(): TreeStatus {
|
|
232
234
|
if (this.disposed) {
|
|
233
235
|
return TreeStatus.Deleted;
|
|
@@ -282,13 +284,12 @@ export class TreeNodeKernel {
|
|
|
282
284
|
* Note that for "marinated" nodes, this FlexTreeNode exists and returns it: it does not return the MapTreeNode which is the current InnerNode.
|
|
283
285
|
*/
|
|
284
286
|
public getOrCreateInnerNode(allowFreed = false): InnerNode {
|
|
285
|
-
if (!(this
|
|
286
|
-
//
|
|
287
|
-
return this.innerNode;
|
|
287
|
+
if (!isHydrated(this.#hydrationState)) {
|
|
288
|
+
return this.#hydrationState.innerNode; // Unhydrated case
|
|
288
289
|
}
|
|
289
290
|
|
|
290
|
-
if (
|
|
291
|
-
return this.innerNode;
|
|
291
|
+
if (this.#hydrationState.innerNode !== undefined) {
|
|
292
|
+
return this.#hydrationState.innerNode; // Cooked case
|
|
292
293
|
}
|
|
293
294
|
|
|
294
295
|
// Marinated case -> cooked
|
|
@@ -296,21 +297,23 @@ export class TreeNodeKernel {
|
|
|
296
297
|
// The proxy is bound to an anchor node, but it may or may not have an actual flex node yet
|
|
297
298
|
const flexNode = anchorNode.slots.get(flexTreeSlot);
|
|
298
299
|
if (flexNode !== undefined) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
300
|
+
// If the flex node already exists, use it...
|
|
301
|
+
this.#hydrationState.innerNode = flexNode;
|
|
302
|
+
} else {
|
|
303
|
+
// ...otherwise, the flex node must be created
|
|
304
|
+
const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context");
|
|
305
|
+
const cursor = context.checkout.forest.allocateCursor("getFlexNode");
|
|
306
|
+
context.checkout.forest.moveCursorToPath(anchorNode, cursor);
|
|
307
|
+
this.#hydrationState.innerNode = makeTree(context, cursor);
|
|
308
|
+
cursor.free();
|
|
309
|
+
// Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode
|
|
310
|
+
anchorForgetters?.get(this.node)?.();
|
|
311
|
+
if (!allowFreed) {
|
|
312
|
+
assertFlexTreeEntityNotFreed(this.#hydrationState.innerNode);
|
|
313
|
+
}
|
|
312
314
|
}
|
|
313
|
-
|
|
315
|
+
|
|
316
|
+
return this.#hydrationState.innerNode;
|
|
314
317
|
}
|
|
315
318
|
|
|
316
319
|
/**
|
|
@@ -344,24 +347,19 @@ export class TreeNodeKernel {
|
|
|
344
347
|
/**
|
|
345
348
|
* Retrieves the InnerNode associated with the given target via {@link setInnerNode}, if any.
|
|
346
349
|
* @remarks
|
|
347
|
-
* If `target` is
|
|
350
|
+
* If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode.
|
|
348
351
|
* If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode.
|
|
349
|
-
* If the target is
|
|
352
|
+
* If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined.
|
|
350
353
|
*/
|
|
351
354
|
public tryGetInnerNode(): InnerNode | undefined {
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (!isHydrated(this.#hydrationState)) {
|
|
358
|
-
return this.innerNode;
|
|
355
|
+
if (isHydrated(this.#hydrationState)) {
|
|
356
|
+
return (
|
|
357
|
+
this.#hydrationState.innerNode ??
|
|
358
|
+
this.#hydrationState.anchorNode.slots.get(flexTreeSlot)
|
|
359
|
+
);
|
|
359
360
|
}
|
|
360
361
|
|
|
361
|
-
|
|
362
|
-
const anchorNode = this.#hydrationState.anchorNode;
|
|
363
|
-
// The proxy is bound to an anchor node, but it may or may not have an actual flex node yet
|
|
364
|
-
return anchorNode.slots.get(flexTreeSlot);
|
|
362
|
+
return this.#hydrationState.innerNode;
|
|
365
363
|
}
|
|
366
364
|
}
|
|
367
365
|
|
|
@@ -47,7 +47,7 @@ import { TreeNodeValid, type MostDerivedData } from "./treeNodeValid.js";
|
|
|
47
47
|
import { getUnhydratedContext } from "./createContext.js";
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Generates the properties for an ObjectNode from its field schema object.
|
|
51
51
|
* @system @public
|
|
52
52
|
*/
|
|
53
53
|
export type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFieldSchema>> = {
|
|
@@ -58,7 +58,9 @@ export interface ObjectNodeSchemaInternalData {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export const ObjectNodeSchema = {
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
|
+
* instanceof-based narrowing support for ObjectNodeSchema in Javascript and TypeScript 5.3 or newer.
|
|
63
|
+
*/
|
|
62
64
|
[Symbol.hasInstance](value: TreeNodeSchema): value is ObjectNodeSchema {
|
|
63
65
|
return isObjectNodeSchema(value);
|
|
64
66
|
},
|