diff --git a/Sources/CohesionKit/EntityStore.swift b/Sources/CohesionKit/EntityStore.swift index 707000a..dd78b36 100644 --- a/Sources/CohesionKit/EntityStore.swift +++ b/Sources/CohesionKit/EntityStore.swift @@ -169,14 +169,7 @@ public class EntityStore { logger?.didFailedToStore(T.self, id: entity.id, error: error) } - for parentRef in node.metadata.parentsRefs { - guard let parentNode = storage[parentRef]?.unwrap() as? AnyEntityNode else { - continue - } - - parentNode.updateEntityRelationship(node) - parentNode.enqueue(in: registry) - } + updateParents(of: node) return node } @@ -191,7 +184,7 @@ public class EntityStore { } for (childRef, _) in node.metadata.childrenRefs { - guard let childNode = storage[childRef]?.unwrap() as? AnyEntityNode else { + guard let childNode = storage[childRef]?.unwrap() as? any AnyEntityNode else { continue } @@ -216,16 +209,21 @@ public class EntityStore { logger?.didFailedToStore(T.self, id: entity.id, error: error) } + updateParents(of: node) + + return node + } + + func updateParents(of node: some AnyEntityNode) { for parentRef in node.metadata.parentsRefs { - guard let parentNode = storage[parentRef]?.unwrap() as? AnyEntityNode else { + guard let parentNode = storage[parentRef]?.unwrap() as? any AnyEntityNode ?? refAliases[parentRef] else { continue } parentNode.updateEntityRelationship(node) parentNode.enqueue(in: registry) + updateParents(of: parentNode) } - - return node } private func storeAlias(content: T?, key: AliasKey, modifiedAt: Stamp?) { diff --git a/Sources/CohesionKit/Storage/AliasStorage.swift b/Sources/CohesionKit/Storage/AliasStorage.swift index a8a341e..f46cf9a 100644 --- a/Sources/CohesionKit/Storage/AliasStorage.swift +++ b/Sources/CohesionKit/Storage/AliasStorage.swift @@ -1,5 +1,5 @@ /// Keep a strong reference on each aliased node -typealias AliasStorage = [String: AnyEntityNode] +typealias AliasStorage = [String: any AnyEntityNode] extension AliasStorage { subscript(_ aliasKey: AliasKey) -> EntityNode>? { @@ -9,7 +9,8 @@ extension AliasStorage { subscript(safe key: AliasKey, onChange onChange: ((EntityNode>) -> Void)? = nil) -> EntityNode> { mutating get { - self[key: key, default: EntityNode(AliasContainer(key: key), modifiedAt: nil, onChange: onChange)] + let storeKey = buildKey(for: T.self, key: key) + return self[key: key, default: EntityNode(AliasContainer(key: key), key: storeKey, modifiedAt: nil, onChange: onChange)] } } diff --git a/Sources/CohesionKit/Storage/EntityNode.swift b/Sources/CohesionKit/Storage/EntityNode.swift index 4ec937d..88cb35b 100644 --- a/Sources/CohesionKit/Storage/EntityNode.swift +++ b/Sources/CohesionKit/Storage/EntityNode.swift @@ -22,32 +22,36 @@ struct EntityMetadata { /// Typed erased protocol protocol AnyEntityNode: AnyObject { + associatedtype Value + + var ref: Observable { get } var value: Any { get } var metadata: EntityMetadata { get } var storageKey: String { get } func nullify() -> Bool - func removeParent(_ node: AnyEntityNode) - func updateEntityRelationship(_ node: EntityNode) + func removeParent(_ node: any AnyEntityNode) + func updateEntityRelationship(_ node: some AnyEntityNode) func enqueue(in: ObserverRegistry) } /// A graph node representing a entity of type `T` and its children. Anytime one of its children is updated the node /// will reflect the change on its own value. class EntityNode: AnyEntityNode { + typealias Value = T /// A child subscription used by its EntityNode parent struct SubscribedChild { /// the child subscription. Use it to unsubscribe to child upates let subscription: Subscription /// the child node value - let node: AnyEntityNode + let node: any AnyEntityNode } var value: Any { ref.value } var metadata = EntityMetadata() // FIXME: to delete, it's "just" to have a strong ref and avoid nodes to be deleted. Need a better memory management - private var childrenNodes: [AnyEntityNode] = [] + private var childrenNodes: [any AnyEntityNode] = [] var applyChildrenChanges = true /// An observable entity reference @@ -110,20 +114,26 @@ class EntityNode: AnyEntityNode { childrenNodes = [] } - func removeParent(_ node: AnyEntityNode) { + func removeParent(_ node: any AnyEntityNode) { metadata.parentsRefs.remove(node.storageKey) } - func updateEntityRelationship(_ node: EntityNode) { + func updateEntityRelationship(_ node: U) { guard let keyPath = metadata.childrenRefs[node.storageKey] else { return } - guard let writableKeyPath = keyPath as? WritableKeyPath else { + if let writableKeyPath = keyPath as? WritableKeyPath { + ref.value[keyPath: writableKeyPath] = node.ref.value + return + } + + if let optionalWritableKeyPath = keyPath as? WritableKeyPath { + ref.value[keyPath: optionalWritableKeyPath] = node.ref.value return } - ref.value[keyPath: writableKeyPath] = node.ref.value + print("CohesionKit: cannot convert \(type(of: keyPath)) to WritableKeyPath<\(T.self), \(U.Value.self)>") } func enqueue(in registry: ObserverRegistry) { diff --git a/Tests/CohesionKitTests/EntityStoreTests.swift b/Tests/CohesionKitTests/EntityStoreTests.swift index d2c0661..381908b 100644 --- a/Tests/CohesionKitTests/EntityStoreTests.swift +++ b/Tests/CohesionKitTests/EntityStoreTests.swift @@ -411,7 +411,7 @@ extension EntityStoreTests { XCTAssertTrue(registry.hasPendingChange(for: AliasContainer.self)) } - func test_update_entityIsIndirectlyUsedByAlias_itEnqueuesAliasInRegistry() { + func test_update_entityIsInsideAggregagte_aggreateIsAliased_itEnqueuesAliasInRegistry() { let aggregate = RootFixture(id: 1, primitive: "", singleNode: SingleNodeFixture(id: 1), listNodes: []) let registry = ObserverRegistryStub() let entityStore = EntityStore(registry: registry) diff --git a/Tests/CohesionKitTests/Visitor/EntityStoreVisitorTests.swift b/Tests/CohesionKitTests/Visitor/EntityStoreVisitorTests.swift index e2e9984..e0ea9e1 100644 --- a/Tests/CohesionKitTests/Visitor/EntityStoreVisitorTests.swift +++ b/Tests/CohesionKitTests/Visitor/EntityStoreVisitorTests.swift @@ -59,8 +59,8 @@ class EntityStoreStoreVisitorTests: XCTestCase { } private class EntityNodeStub: EntityNode { - var observeChildKeyPathCalled: (AnyEntityNode, PartialKeyPath) -> Void = { _, _ in } - var observeChildKeyPathOptionalCalled: (AnyEntityNode, PartialKeyPath) -> Void = { _, _ in } + var observeChildKeyPathCalled: (any AnyEntityNode, PartialKeyPath) -> Void = { _, _ in } + var observeChildKeyPathOptionalCalled: (any AnyEntityNode, PartialKeyPath) -> Void = { _, _ in } override func observeChild(_ childNode: EntityNode, for keyPath: WritableKeyPath) { observeChildKeyPathCalled(childNode, keyPath)