diff --git a/model-api-gen-gradle-test/typescript-generation/package-lock.json b/model-api-gen-gradle-test/typescript-generation/package-lock.json index 0b182f267e..e49b04ccb2 100644 --- a/model-api-gen-gradle-test/typescript-generation/package-lock.json +++ b/model-api-gen-gradle-test/typescript-generation/package-lock.json @@ -1130,7 +1130,7 @@ "node_modules/@modelix/model-client": { "version": "8.2.1-1-g563e21c.dirty-SNAPSHOT", "resolved": "file:../../model-client/build/npmDevPackage/model-client.tgz", - "integrity": "sha512-1Qabw/BnJnvACjJx54ucPdYxepL9Qox3tp2QjzbSTjQfhjra9tA0kI34nZOdX8rD03NslmWxp32pmD+xSAvt8g==", + "integrity": "sha512-zQK7xz+u1Y4YSz76bhItsNweeC8p8dBzHE8WV2O11lEKGrFJq/98pK28CdsgpXE2KcNCxrzWhWTN3+RqPEGtyQ==", "dependencies": { "@aws-crypto/sha256-js": "^5.0.0", "@js-joda/core": "3.2.0", @@ -5587,7 +5587,7 @@ }, "@modelix/model-client": { "version": "file:../../model-client/build/npmDevPackage/model-client.tgz", - "integrity": "sha512-1Qabw/BnJnvACjJx54ucPdYxepL9Qox3tp2QjzbSTjQfhjra9tA0kI34nZOdX8rD03NslmWxp32pmD+xSAvt8g==", + "integrity": "sha512-zQK7xz+u1Y4YSz76bhItsNweeC8p8dBzHE8WV2O11lEKGrFJq/98pK28CdsgpXE2KcNCxrzWhWTN3+RqPEGtyQ==", "requires": { "@aws-crypto/sha256-js": "^5.0.0", "@js-joda/core": "3.2.0", diff --git a/model-api-gen-gradle-test/typescript-generation/src/test-helpers.ts b/model-api-gen-gradle-test/typescript-generation/src/test-helpers.ts index d9145085a5..af52036527 100644 --- a/model-api-gen-gradle-test/typescript-generation/src/test-helpers.ts +++ b/model-api-gen-gradle-test/typescript-generation/src/test-helpers.ts @@ -23,8 +23,6 @@ export function useFakeRootNode(nodeData: object = DEFAULT_NODE_DATA) { const { loadModelsFromJson } = org.modelix.model.client2; const rootNode = loadModelsFromJson( [JSON.stringify(nodeData)], - // for the purpose of the test a change handler is not needed - () => {} ); function getUntypedNode(role: string = "children1") { diff --git a/model-client/src/jsMain/kotlin/org/modelix/model/client2/BranchJSImpl.kt b/model-client/src/jsMain/kotlin/org/modelix/model/client2/BranchJSImpl.kt index 32860b6231..e3894e3c1a 100644 --- a/model-client/src/jsMain/kotlin/org/modelix/model/client2/BranchJSImpl.kt +++ b/model-client/src/jsMain/kotlin/org/modelix/model/client2/BranchJSImpl.kt @@ -14,13 +14,19 @@ * limitations under the License. */ +@file:OptIn(UnstableModelixFeature::class) + package org.modelix.model.client2 import INodeJS import INodeReferenceJS import org.modelix.kotlin.utils.UnstableModelixFeature import org.modelix.model.api.IBranch +import org.modelix.model.api.IBranchListener import org.modelix.model.api.INodeReferenceSerializer +import org.modelix.model.api.ITree +import org.modelix.model.api.ITreeChangeVisitor +import org.modelix.model.api.PNodeAdapter import org.modelix.model.api.getRootNode import org.modelix.model.area.getArea @@ -34,7 +40,19 @@ class BranchJSImpl( private val branch: IBranch, ) : BranchJS { + private val changeHandlers = mutableSetOf() + private val jsRootNode = toNodeJs(branch.getRootNode()) + private val changeListener = ChangeListener(branch) { change -> + changeHandlers.forEach { + changeHandler -> + changeHandler(change) + } + } + + init { + branch.addListener(changeListener) + } override val rootNode: INodeJS get() { @@ -46,7 +64,51 @@ class BranchJSImpl( return branch.getArea().resolveNode(referenceObject)?.let(::toNodeJs) } + override fun addListener(handler: ChangeHandler) { + changeHandlers.add(handler) + } + override fun removeListener(handler: ChangeHandler) { + changeHandlers.remove(handler) + } + override fun dispose() { dispose.invoke() } } + +class ChangeListener(private val branch: IBranch, private val changeCallback: (ChangeJS) -> Unit) : IBranchListener { + + fun nodeIdToInode(nodeId: Long): INodeJS { + return toNodeJs(PNodeAdapter(nodeId, branch)) + } + + override fun treeChanged(oldTree: ITree?, newTree: ITree) { + if (oldTree == null) { + return + } + newTree.visitChanges( + oldTree, + object : ITreeChangeVisitor { + override fun containmentChanged(nodeId: Long) { + changeCallback(ContainmentChanged(nodeIdToInode(nodeId))) + } + + override fun conceptChanged(nodeId: Long) { + changeCallback(ConceptChanged(nodeIdToInode(nodeId))) + } + + override fun childrenChanged(nodeId: Long, role: String?) { + changeCallback(ChildrenChanged(nodeIdToInode(nodeId), role)) + } + + override fun referenceChanged(nodeId: Long, role: String) { + changeCallback(ReferenceChanged(nodeIdToInode(nodeId), role)) + } + + override fun propertyChanged(nodeId: Long, role: String) { + changeCallback(PropertyChanged(nodeIdToInode(nodeId), role)) + } + }, + ) + } +} diff --git a/model-client/src/jsMain/kotlin/org/modelix/model/client2/ClientJS.kt b/model-client/src/jsMain/kotlin/org/modelix/model/client2/ClientJS.kt index 1431887de9..26eabfba66 100644 --- a/model-client/src/jsMain/kotlin/org/modelix/model/client2/ClientJS.kt +++ b/model-client/src/jsMain/kotlin/org/modelix/model/client2/ClientJS.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(UnstableModelixFeature::class) +@file:OptIn(UnstableModelixFeature::class, UnstableModelixFeature::class) package org.modelix.model.client2 @@ -25,13 +25,8 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.promise import org.modelix.kotlin.utils.UnstableModelixFeature import org.modelix.model.ModelFacade -import org.modelix.model.api.IBranch -import org.modelix.model.api.IBranchListener import org.modelix.model.api.INode -import org.modelix.model.api.ITree -import org.modelix.model.api.ITreeChangeVisitor import org.modelix.model.api.JSNodeConverter -import org.modelix.model.api.PNodeAdapter import org.modelix.model.data.ModelData import org.modelix.model.lazy.RepositoryId import org.modelix.model.withAutoTransactions @@ -43,11 +38,8 @@ import kotlin.js.Promise intendedFinalization = "The client is intended to be finalized when the overarching task is finished.", ) @JsExport -fun loadModelsFromJson( - json: Array, - changeCallback: (ChangeJS) -> Unit, -): INodeJS { - val branch = loadModelsFromJsonAsBranch(json, changeCallback) +fun loadModelsFromJson(json: Array): INodeJS { + val branch = loadModelsFromJsonAsBranch(json) return branch.rootNode } @@ -56,13 +48,9 @@ fun loadModelsFromJson( intendedFinalization = "The client is intended to be finalized when the overarching task is finished.", ) @JsExport -fun loadModelsFromJsonAsBranch( - json: Array, - changeCallback: (ChangeJS) -> Unit, -): BranchJS { +fun loadModelsFromJsonAsBranch(json: Array): BranchJS { val branch = ModelFacade.toLocalBranch(ModelFacade.newLocalTree()) json.forEach { ModelData.fromJson(it).load(branch) } - branch.addListener(ChangeListener(branch, changeCallback)) return BranchJSImpl({}, branch.withAutoTransactions()) } @@ -88,11 +76,7 @@ fun connectClient(url: String): Promise { interface ClientJS { fun dispose() - fun connectBranch( - repositoryId: String, - branchId: String, - changeCallback: (ChangeJS) -> Unit, - ): Promise + fun connectBranch(repositoryId: String, branchId: String): Promise fun fetchBranches(repositoryId: String): Promise> @@ -121,18 +105,13 @@ class ClientJSImpl(private val modelClient: ModelClientV2) : ClientJS { } @DelicateCoroutinesApi - override fun connectBranch( - repositoryId: String, - branchId: String, - changeCallback: (ChangeJS) -> Unit, - ): Promise { + override fun connectBranch(repositoryId: String, branchId: String): Promise { return GlobalScope.promise { val modelClient = modelClient val branchReference = RepositoryId(repositoryId).getBranchReference(branchId) val model: ReplicatedModel = modelClient.getReplicatedModel(branchReference) model.start() val branch = model.getBranch() - branch.addListener(ChangeListener(branch, changeCallback)) val branchWithAutoTransaction = branch.withAutoTransactions() return@promise BranchJSImpl({ model.dispose() }, branchWithAutoTransaction) } @@ -143,6 +122,8 @@ class ClientJSImpl(private val modelClient: ModelClientV2) : ClientJS { } } +typealias ChangeHandler = (ChangeJS) -> Unit + @UnstableModelixFeature( reason = "The overarching task https://issues.modelix.org/issue/MODELIX-500 is in development.", intendedFinalization = "The client is intended to be finalized when the overarching task is finished.", @@ -152,43 +133,8 @@ interface BranchJS { val rootNode: INodeJS fun dispose() fun resolveNode(reference: INodeReferenceJS): INodeJS? -} - -class ChangeListener(private val branch: IBranch, private val changeCallback: (ChangeJS) -> Unit) : IBranchListener { - - fun nodeIdToInode(nodeId: Long): INodeJS { - return toNodeJs(PNodeAdapter(nodeId, branch)) - } - - override fun treeChanged(oldTree: ITree?, newTree: ITree) { - if (oldTree == null) { - return - } - newTree.visitChanges( - oldTree, - object : ITreeChangeVisitor { - override fun containmentChanged(nodeId: Long) { - changeCallback(ContainmentChanged(nodeIdToInode(nodeId))) - } - - override fun conceptChanged(nodeId: Long) { - changeCallback(ConceptChanged(nodeIdToInode(nodeId))) - } - - override fun childrenChanged(nodeId: Long, role: String?) { - changeCallback(ChildrenChanged(nodeIdToInode(nodeId), role)) - } - - override fun referenceChanged(nodeId: Long, role: String) { - changeCallback(ReferenceChanged(nodeIdToInode(nodeId), role)) - } - - override fun propertyChanged(nodeId: Long, role: String) { - changeCallback(PropertyChanged(nodeIdToInode(nodeId), role)) - } - }, - ) - } + fun addListener(handler: ChangeHandler) + fun removeListener(handler: ChangeHandler) } fun toNodeJs(rootNode: INode) = JSNodeConverter.nodeToJs(rootNode).unsafeCast() diff --git a/model-client/src/jsTest/kotlin/org/modelix/model/client2/BranchJSTest.kt b/model-client/src/jsTest/kotlin/org/modelix/model/client2/BranchJSTest.kt index cf62f84ecd..1ba7aa73d2 100644 --- a/model-client/src/jsTest/kotlin/org/modelix/model/client2/BranchJSTest.kt +++ b/model-client/src/jsTest/kotlin/org/modelix/model/client2/BranchJSTest.kt @@ -16,6 +16,7 @@ package org.modelix.model.client2 +import GeneratedConcept import org.modelix.kotlin.utils.UnstableModelixFeature import kotlin.test.Test import kotlin.test.assertEquals @@ -24,10 +25,14 @@ import kotlin.test.assertNull @OptIn(UnstableModelixFeature::class) class BranchJSTest { - @Test - fun canResolveNode() { - // Arrange - val data = """ + private val emptyRoot = """ + { + "root": { + } + } + """.trimIndent() + + private val rootWithChild = """ { "root": { "children": [ @@ -37,8 +42,12 @@ class BranchJSTest { ] } } - """.trimIndent() - val branch = loadModelsFromJsonAsBranch(arrayOf(data)) {} + """.trimIndent() + + @Test + fun canResolveNode() { + // Arrange + val branch = loadModelsFromJsonAsBranch(arrayOf(rootWithChild)) val aNode = branch.rootNode.getAllChildren()[0] val aNodeReference = aNode.getReference() @@ -63,7 +72,7 @@ class BranchJSTest { } } """.trimIndent() - val branch = loadModelsFromJsonAsBranch(arrayOf(data)) {} + val branch = loadModelsFromJsonAsBranch(arrayOf(data)) val aNode = branch.rootNode.getAllChildren()[0] val aNodeReference = aNode.getReference() branch.rootNode.removeChild(aNode) @@ -74,4 +83,140 @@ class BranchJSTest { // Assert assertNull(resolvedNode) } + + @Test + fun changeHandlerCanBeAdded() { + val branch = loadModelsFromJsonAsBranch(arrayOf(rootWithChild)) + var changeCount = 0 + val changeListener: ChangeHandler = { _ -> changeCount++ } + branch.addListener(changeListener) + + val aNode = branch.rootNode.getAllChildren()[0] + branch.rootNode.removeChild(aNode) + + assertEquals(1, changeCount) + } + + @Test + fun changeHandlerCanBeRemoved() { + val branch = loadModelsFromJsonAsBranch(arrayOf(rootWithChild)) + var changeCount = 0 + val changeListener: ChangeHandler = { _ -> changeCount++ } + branch.addListener(changeListener) + branch.removeListener(changeListener) + + val aNode = branch.rootNode.getAllChildren()[0] + branch.rootNode.removeChild(aNode) + + assertEquals(0, changeCount) + } + + @Test + fun changeDetectionWorksForPropertyUpdate() { + // Arrange + var propertyChanged = 0 + val branch = loadModelsFromJsonAsBranch(arrayOf(emptyRoot)) + branch.addListener { + when (it) { + is PropertyChanged -> propertyChanged++ + else -> {} + } + } + val rootNode = branch.rootNode + + // Act + rootNode.setPropertyValue("aProperty", "aValue") + + // Assert + assertEquals(1, propertyChanged) + } + + @Test + fun changeDetectionWorksForReferenceUpdate() { + // Arrange + var referenceChanged = 0 + val branch = loadModelsFromJsonAsBranch(arrayOf(emptyRoot)) + branch.addListener { + when (it) { + is ReferenceChanged -> referenceChanged++ + else -> {} + } + } + val rootNode = branch.rootNode + + // Act + rootNode.setReferenceTargetNode("aRef", rootNode) + + // Assert + assertEquals(1, referenceChanged) + } + + @Test + fun changeDetectionWorksForAddedChild() { + // Arrange + var childrenChanged = 0 + val branch = loadModelsFromJsonAsBranch(arrayOf(emptyRoot)) + branch.addListener { + when (it) { + is ChildrenChanged -> childrenChanged++ + else -> {} + } + } + val rootNode = branch.rootNode + + // Act + rootNode.addNewChild("aRole", -1, GeneratedConcept("aConceptUid")) + + // Assert + assertEquals(1, childrenChanged) + } + + @Test + fun changeDetectionWorksForMovedChild() { + // Arrange + var childrenChanged = 0 + var containmentChanged = 0 + val branch = loadModelsFromJsonAsBranch(arrayOf(emptyRoot)) + branch.addListener { + when (it) { + is ChildrenChanged -> childrenChanged++ + is ContainmentChanged -> containmentChanged++ + else -> {} + } + } + val rootNode = branch.rootNode + val childNode = rootNode.addNewChild("aRole", -1, GeneratedConcept("aConceptUid")) + childrenChanged = 0 + + // Act + rootNode.moveChild("anotherRole", -1, childNode) + + // Assert + assertEquals(2, childrenChanged) + assertEquals(1, containmentChanged) + } + + @Test + fun changeDetectionWorksForRemovedChild() { + // Arrange + var childrenChanged = 0 + var containmentChanged = 0 + val branch = loadModelsFromJsonAsBranch(arrayOf(emptyRoot)) + branch.addListener { + when (it) { + is ChildrenChanged -> childrenChanged++ + is ContainmentChanged -> containmentChanged++ + else -> {} + } + } + val rootNode = branch.rootNode + val childNode = rootNode.addNewChild("aRole", -1, GeneratedConcept("aConceptUid")) + childrenChanged = 0 + + // Act + rootNode.removeChild(childNode) + + // Assert + assertEquals(1, childrenChanged) + } } diff --git a/model-client/src/jsTest/kotlin/org/modelix/model/client2/ClientJSTest.kt b/model-client/src/jsTest/kotlin/org/modelix/model/client2/ClientJSTest.kt index 9971c36d72..bab83eff1d 100644 --- a/model-client/src/jsTest/kotlin/org/modelix/model/client2/ClientJSTest.kt +++ b/model-client/src/jsTest/kotlin/org/modelix/model/client2/ClientJSTest.kt @@ -34,7 +34,7 @@ class ClientJSTest { @Test fun canAddChildrenWithUnregisteredConcept() { // Arrange - val rootNode = loadModelsFromJson(arrayOf(emptyRoot)) {} + val rootNode = loadModelsFromJson(arrayOf(emptyRoot)) val jsConcept = GeneratedConcept("aConceptUid") // Act @@ -48,105 +48,6 @@ class ClientJSTest { assertEquals("aConceptUid", children.get(0).getConceptUID()) } - @Test - fun changeDetectionWorksForPropertyUpdate() { - // Arrange - var propertyChanged = 0 - val rootNode = loadModelsFromJson(arrayOf(emptyRoot)) { - when (it) { - is PropertyChanged -> propertyChanged++ - else -> {} - } - } - - // Act - rootNode.setPropertyValue("aProperty", "aValue") - - // Assert - assertEquals(1, propertyChanged) - } - - @Test - fun changeDetectionWorksForReferenceUpdate() { - // Arrange - var referenceChanged = 0 - val rootNode = loadModelsFromJson(arrayOf(emptyRoot)) { - when (it) { - is ReferenceChanged -> referenceChanged++ - else -> {} - } - } - - // Act - rootNode.setReferenceTargetNode("aRef", rootNode) - - // Assert - assertEquals(1, referenceChanged) - } - - @Test - fun changeDetectionWorksForAddedChild() { - // Arrange - var childrenChanged = 0 - val rootNode = loadModelsFromJson(arrayOf(emptyRoot)) { - when (it) { - is ChildrenChanged -> childrenChanged++ - else -> {} - } - } - - // Act - rootNode.addNewChild("aRole", -1, GeneratedConcept("aConceptUid")) - - // Assert - assertEquals(1, childrenChanged) - } - - @Test - fun changeDetectionWorksForMovedChild() { - // Arrange - var childrenChanged = 0 - var containmentChanged = 0 - val rootNode = loadModelsFromJson(arrayOf(emptyRoot)) { - when (it) { - is ChildrenChanged -> childrenChanged++ - is ContainmentChanged -> containmentChanged++ - else -> {} - } - } - val childNode = rootNode.addNewChild("aRole", -1, GeneratedConcept("aConceptUid")) - childrenChanged = 0 - - // Act - rootNode.moveChild("anotherRole", -1, childNode) - - // Assert - assertEquals(2, childrenChanged) - assertEquals(1, containmentChanged) - } - - @Test - fun changeDetectionWorksForRemovedChild() { - // Arrange - var childrenChanged = 0 - var containmentChanged = 0 - val rootNode = loadModelsFromJson(arrayOf(emptyRoot)) { - when (it) { - is ChildrenChanged -> childrenChanged++ - is ContainmentChanged -> containmentChanged++ - else -> {} - } - } - val childNode = rootNode.addNewChild("aRole", -1, GeneratedConcept("aConceptUid")) - childrenChanged = 0 - - // Act - rootNode.removeChild(childNode) - - // Assert - assertEquals(1, childrenChanged) - } - @Test fun canResolveReference() { // Arrange @@ -164,7 +65,7 @@ class ClientJSTest { } } """.trimIndent() - val rootNode = loadModelsFromJson(arrayOf(data), {}) + val rootNode = loadModelsFromJson(arrayOf(data)) val child0 = rootNode.getAllChildren()[0] val child1 = rootNode.getAllChildren()[1] diff --git a/vue-model-api/package-lock.json b/vue-model-api/package-lock.json index ace37e12b0..555705ce8b 100644 --- a/vue-model-api/package-lock.json +++ b/vue-model-api/package-lock.json @@ -1281,7 +1281,7 @@ "node_modules/@modelix/model-client": { "version": "8.2.1-1-g563e21c.dirty-SNAPSHOT", "resolved": "file:../model-client/build/npmDevPackage/model-client.tgz", - "integrity": "sha512-1Qabw/BnJnvACjJx54ucPdYxepL9Qox3tp2QjzbSTjQfhjra9tA0kI34nZOdX8rD03NslmWxp32pmD+xSAvt8g==", + "integrity": "sha512-zQK7xz+u1Y4YSz76bhItsNweeC8p8dBzHE8WV2O11lEKGrFJq/98pK28CdsgpXE2KcNCxrzWhWTN3+RqPEGtyQ==", "dependencies": { "@aws-crypto/sha256-js": "^5.0.0", "@js-joda/core": "3.2.0", diff --git a/vue-model-api/src/useModelsFromJson.ts b/vue-model-api/src/useModelsFromJson.ts index 7abbac568e..149d1f1d9c 100644 --- a/vue-model-api/src/useModelsFromJson.ts +++ b/vue-model-api/src/useModelsFromJson.ts @@ -4,7 +4,7 @@ import { Cache } from "./internal/Cache"; import { handleChange } from "./internal/handleChange"; import { org } from "@modelix/model-client"; -const { loadModelsFromJson } = org.modelix.model.client2; +const { loadModelsFromJsonAsBranch } = org.modelix.model.client2; type ChangeJS = org.modelix.model.client2.ChangeJS; @@ -20,12 +20,10 @@ type ChangeJS = org.modelix.model.client2.ChangeJS; */ export function useModelsFromJson(modelDataJsonStrings: string[]): INodeJS { const cache = new Cache(); - const unreactiveRootNode = loadModelsFromJson( - modelDataJsonStrings, - (change: ChangeJS) => { - handleChange(change, cache); - }, - ); - const reactiveRootNode = toReactiveINodeJS(unreactiveRootNode, cache); + const branch = loadModelsFromJsonAsBranch(modelDataJsonStrings); + branch.addListener((change: ChangeJS) => { + handleChange(change, cache); + }); + const reactiveRootNode = toReactiveINodeJS(branch.rootNode, cache); return reactiveRootNode; } diff --git a/vue-model-api/src/useRootNode.test.ts b/vue-model-api/src/useRootNode.test.ts index 846130d76b..1cf2b69814 100644 --- a/vue-model-api/src/useRootNode.test.ts +++ b/vue-model-api/src/useRootNode.test.ts @@ -13,27 +13,22 @@ test("test branch connects", (done) => { class SuccessfulBranchJS { public rootNode: INodeJS; - constructor(branchId: string, changeCallback: (change: ChangeJS) => void) { + constructor(branchId: string) { const root = { root: {}, }; - this.rootNode = loadModelsFromJson( - [JSON.stringify(root)], - changeCallback, - ); + this.rootNode = loadModelsFromJson([JSON.stringify(root)]); this.rootNode.setPropertyValue("branchId", branchId); } + + addListener = jest.fn(); } class SuccessfulClientJS { - connectBranch( - _repositoryId: string, - branchId: string, - changeCallback: (change: ChangeJS) => void, - ): Promise { + connectBranch(_repositoryId: string, branchId: string): Promise { return Promise.resolve( - new SuccessfulBranchJS(branchId, changeCallback) as BranchJS, + new SuccessfulBranchJS(branchId) as unknown as BranchJS, ); } } diff --git a/vue-model-api/src/useRootNode.ts b/vue-model-api/src/useRootNode.ts index 50f4232284..409309893a 100644 --- a/vue-model-api/src/useRootNode.ts +++ b/vue-model-api/src/useRootNode.ts @@ -68,17 +68,18 @@ export function useRootNode( } const cache = new Cache(); return clientValue - .connectBranch(repositoryIdValue, branchIdValue, (change: ChangeJS) => { - if (cache === null) { - throw Error("The cache is unexpectedly not set up."); - } - handleChange(change, cache); - }) + .connectBranch(repositoryIdValue, branchIdValue) .then((branch) => ({ branch, cache })); }, ({ branch: connectedBranch, cache }, isResultOfLastStartedPromise) => { if (isResultOfLastStartedPromise) { branch = connectedBranch; + branch.addListener((change: ChangeJS) => { + if (cache === null) { + throw Error("The cache is unexpectedly not set up."); + } + handleChange(change, cache); + }); const unreactiveRootNode = branch.rootNode; const reactiveRootNode = toReactiveINodeJS(unreactiveRootNode, cache); rootNodeRef.value = reactiveRootNode;