Skip to content

Commit

Permalink
feat(store): Make stamp optional (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjechris authored Jul 24, 2023
1 parent c2e9b21 commit 6fb3e74
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 23 deletions.
30 changes: 16 additions & 14 deletions Sources/CohesionKit/Identity/IdentityStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 32,7 @@ public class IdentityMap {
public func store<T: Identifiable>(
entity: T,
named: AliasKey<T>? = nil,
modifiedAt: Stamp = Date().stamp,
modifiedAt: Stamp? = nil,
ifPresent update: Update<T>? = nil
) -> EntityObserver<T> {
identityQueue.sync(flags: .barrier) {
Expand Down Expand Up @@ -64,7 64,7 @@ public class IdentityMap {
public func store<T: Aggregate>(
entity: T,
named: AliasKey<T>? = nil,
modifiedAt: Stamp = Date().stamp,
modifiedAt: Stamp? = nil,
ifPresent update: Update<T>? = nil
) -> EntityObserver<T> {
identityQueue.sync(flags: .barrier) {
Expand All @@ -86,7 86,7 @@ public class IdentityMap {
}

/// Store multiple entities at once
public func store<C: Collection>(entities: C, named: AliasKey<C>? = nil, modifiedAt: Stamp = Date().stamp)
public func store<C: Collection>(entities: C, named: AliasKey<C>? = nil, modifiedAt: Stamp? = nil)
-> [EntityObserver<C.Element>] where C.Element: Identifiable {
identityQueue.sync(flags: .barrier) {
let nodes = entities.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }
Expand All @@ -101,7 101,7 @@ public class IdentityMap {
}

/// store multiple aggregates at once
public func store<C: Collection>(entities: C, named: AliasKey<C>? = nil, modifiedAt: Stamp = Date().stamp)
public func store<C: Collection>(entities: C, named: AliasKey<C>? = nil, modifiedAt: Stamp? = nil)
-> [EntityObserver<C.Element>] where C.Element: Aggregate {
identityQueue.sync(flags: .barrier) {
let nodes = entities.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }
Expand Down Expand Up @@ -145,7 145,7 @@ public class IdentityMap {
}
}

func nodeStore<T: Identifiable>(entity: T, modifiedAt: Stamp) -> EntityNode<T> {
func nodeStore<T: Identifiable>(entity: T, modifiedAt: Stamp?) -> EntityNode<T> {
let node = storage[entity, new: EntityNode(entity, modifiedAt: nil)]

do {
Expand All @@ -159,7 159,7 @@ public class IdentityMap {
return node
}

func nodeStore<T: Aggregate>(entity: T, modifiedAt: Stamp) -> EntityNode<T> {
func nodeStore<T: Aggregate>(entity: T, modifiedAt: Stamp?) -> EntityNode<T> {
let node = storage[entity, new: EntityNode(entity, modifiedAt: nil)]

// disable changes while doing the entity update
Expand Down Expand Up @@ -197,7 197,7 @@ extension IdentityMap {
///
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
@discardableResult
public func update<T: Identifiable>(_ type: T.Type, id: T.ID, modifiedAt: Stamp = Date().stamp, update: Update<T>) -> Bool {
public func update<T: Identifiable>(_ type: T.Type, id: T.ID, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
identityQueue.sync(flags: .barrier) {
guard var entity = storage[T.self, id: id]?.ref.value else {
return false
Expand All @@ -218,7 218,7 @@ extension IdentityMap {
///
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
@discardableResult
public func update<T: Aggregate>(_ type: T.Type, id: T.ID, modifiedAt: Stamp = Date().stamp, _ update: Update<T>) -> Bool {
public func update<T: Aggregate>(_ type: T.Type, id: T.ID, modifiedAt: Stamp? = nil, _ update: Update<T>) -> Bool {
identityQueue.sync(flags: .barrier) {
guard var entity = storage[T.self, id: id]?.ref.value else {
return false
Expand All @@ -237,7 237,7 @@ extension IdentityMap {
/// the change was applied
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
@discardableResult
public func update<T: Identifiable>(named: AliasKey<T>, modifiedAt: Stamp = Date().stamp, update: Update<T>) -> Bool {
public func update<T: Identifiable>(named: AliasKey<T>, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
identityQueue.sync(flags: .barrier) {
guard let entity = refAliases[named].value else {
return false
Expand All @@ -259,7 259,7 @@ extension IdentityMap {
/// the change was applied
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
@discardableResult
public func update<T: Aggregate>(named: AliasKey<T>, modifiedAt: Stamp = Date().stamp, update: Update<T>) -> Bool {
public func update<T: Aggregate>(named: AliasKey<T>, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
identityQueue.sync(flags: .barrier) {
guard let entity = refAliases[named].value else {
return false
Expand All @@ -281,7 281,8 @@ extension IdentityMap {
/// the change was applied
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
@discardableResult
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp = Date().stamp, update: Update<[C.Element]>) -> Bool where C.Element: Identifiable {
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<[C.Element]>)
-> Bool where C.Element: Identifiable {
identityQueue.sync(flags: .barrier) {
guard let entities = refAliases[named].value else {
return false
Expand All @@ -304,7 305,8 @@ extension IdentityMap {
/// the change was applied
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
@discardableResult
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp = Date().stamp, update: Update<[C.Element]>) -> Bool where C.Element: Aggregate {
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<[C.Element]>)
-> Bool where C.Element: Aggregate {
identityQueue.sync(flags: .barrier) {
guard let entities = refAliases[named].value else {
return false
Expand Down Expand Up @@ -343,8 345,8 @@ extension IdentityMap {
refAliases.removeAll()
}

/// Removes all alias AND all objects stored weakly. You should not need this method and rather use `removeAlias`. But this can be useful
/// if you fear retain cycles
/// Removes all alias AND all objects stored weakly. You should not need this method and rather use `removeAlias`.
/// But this can be useful if you fear retain cycles
public func removeAll() {
refAliases.removeAll()
storage.removeAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 3,7 @@ import Combine
/// A `KeyPath` wrapper allowing only `Identifiable`/`Aggregate` keypaths
public struct PartialIdentifiableKeyPath<Root> {
let keyPath: PartialKeyPath<Root>
let accept: (EntityNode<Root>, Root, Stamp, NestedEntitiesVisitor) -> Void
let accept: (EntityNode<Root>, Root, Stamp?, NestedEntitiesVisitor) -> Void

/// Creates an instance referencing an `Identifiable` keyPath
public init<T: Identifiable>(_ keyPath: WritableKeyPath<Root, T>) {
Expand Down
8 changes: 4 additions & 4 deletions Sources/CohesionKit/Storage/EntityNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 37,15 @@ class EntityNode<T>: AnyEntityNode {
self.init(ref: Observable(value: entity), modifiedAt: modifiedAt)
}

/// change the entity to a new value only if `modifiedAt` is equal than any previous registered modification
/// change the entity to a new value. If modifiedAt is nil or > to previous date update the value will be changed
/// - Parameter entity the new entity value
/// - Parameter modifiedAt the new entity stamp
func updateEntity(_ newEntity: T, modifiedAt newModifiedAt: Stamp) throws {
if let modifiedAt, newModifiedAt <= modifiedAt {
func updateEntity(_ newEntity: T, modifiedAt newModifiedAt: Stamp?) throws {
if let newModifiedAt, let modifiedAt, newModifiedAt <= modifiedAt {
throw StampError.tooOld(current: modifiedAt, received: newModifiedAt)
}

modifiedAt = newModifiedAt
modifiedAt = newModifiedAt ?? modifiedAt
ref.value = newEntity
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/CohesionKit/Visitor/EntityContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 4,5 @@ import Foundation
struct EntityContext<Root, Value> {
let parent: EntityNode<Root>
let keyPath: WritableKeyPath<Root, Value>
let stamp: Stamp
let stamp: Stamp?
}
27 changes: 24 additions & 3 deletions Tests/CohesionKitTests/Storage/EntityNodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 47,34 @@ class EntityNodeTests: XCTestCase {
XCTAssertEqual(node.value as? RootFixture, startEntity)
}

func test_updateEntity_stampIsNil_entityIsUpdated() throws {
try node.updateEntity(newEntity, modifiedAt: nil)

XCTAssertEqual(node.value as? RootFixture, newEntity)
}

func test_updateEntity_stampIsNil_stampIsNotUpdated() throws {
let badEntity = RootFixture(id: 1, primitive: "wrong update", singleNode: .init(id: 1), listNodes: [])

try node.updateEntity(newEntity, modifiedAt: nil)

XCTAssertThrowsError(try node.updateEntity(badEntity, modifiedAt: startTimestamp - 1)) { error in
switch error {
case StampError.tooOld(let current, _):
XCTAssertEqual(current, startTimestamp)
default:
XCTFail("Wrong error thrown")
}
}
}

func test_observeChild_childChange_entityIsUpdated() throws {
let childNode = EntityNode(startEntity.singleNode, modifiedAt: 0)
let childNode = EntityNode(startEntity.singleNode, modifiedAt: nil)
let newChild = SingleNodeFixture(id: 1, primitive: "updated")

node.observeChild(childNode, for: \.singleNode)

try childNode.updateEntity(newChild, modifiedAt: 1)
try childNode.updateEntity(newChild, modifiedAt: nil)

XCTAssertEqual((node.value as? RootFixture)?.singleNode, newChild)
}
Expand All @@ -71,7 92,7 @@ class EntityNodeTests: XCTestCase {
node = EntityNode(ref: entityRef, modifiedAt: startTimestamp)
node.observeChild(childNode, for: \.singleNode)

try childNode.updateEntity(newChild, modifiedAt: startTimestamp 1)
try childNode.updateEntity(newChild, modifiedAt: nil)

subscription.unsubscribe()

Expand Down

0 comments on commit 6fb3e74

Please sign in to comment.