Skip to content

Commit 6fb3e74

Browse files
author
pjechris
authored
feat(store): Make stamp optional (#52)
1 parent c2e9b21 commit 6fb3e74

File tree

5 files changed

+46
-23
lines changed

5 files changed

+46
-23
lines changed

Sources/CohesionKit/Identity/IdentityStore.swift

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class IdentityMap {
3232
public func store<T: Identifiable>(
3333
entity: T,
3434
named: AliasKey<T>? = nil,
35-
modifiedAt: Stamp = Date().stamp,
35+
modifiedAt: Stamp? = nil,
3636
ifPresent update: Update<T>? = nil
3737
) -> EntityObserver<T> {
3838
identityQueue.sync(flags: .barrier) {
@@ -64,7 +64,7 @@ public class IdentityMap {
6464
public func store<T: Aggregate>(
6565
entity: T,
6666
named: AliasKey<T>? = nil,
67-
modifiedAt: Stamp = Date().stamp,
67+
modifiedAt: Stamp? = nil,
6868
ifPresent update: Update<T>? = nil
6969
) -> EntityObserver<T> {
7070
identityQueue.sync(flags: .barrier) {
@@ -86,7 +86,7 @@ public class IdentityMap {
8686
}
8787

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

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

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

151151
do {
@@ -159,7 +159,7 @@ public class IdentityMap {
159159
return node
160160
}
161161

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

165165
// disable changes while doing the entity update
@@ -197,7 +197,7 @@ extension IdentityMap {
197197
///
198198
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
199199
@discardableResult
200-
public func update<T: Identifiable>(_ type: T.Type, id: T.ID, modifiedAt: Stamp = Date().stamp, update: Update<T>) -> Bool {
200+
public func update<T: Identifiable>(_ type: T.Type, id: T.ID, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
201201
identityQueue.sync(flags: .barrier) {
202202
guard var entity = storage[T.self, id: id]?.ref.value else {
203203
return false
@@ -218,7 +218,7 @@ extension IdentityMap {
218218
///
219219
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
220220
@discardableResult
221-
public func update<T: Aggregate>(_ type: T.Type, id: T.ID, modifiedAt: Stamp = Date().stamp, _ update: Update<T>) -> Bool {
221+
public func update<T: Aggregate>(_ type: T.Type, id: T.ID, modifiedAt: Stamp? = nil, _ update: Update<T>) -> Bool {
222222
identityQueue.sync(flags: .barrier) {
223223
guard var entity = storage[T.self, id: id]?.ref.value else {
224224
return false
@@ -237,7 +237,7 @@ extension IdentityMap {
237237
/// the change was applied
238238
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
239239
@discardableResult
240-
public func update<T: Identifiable>(named: AliasKey<T>, modifiedAt: Stamp = Date().stamp, update: Update<T>) -> Bool {
240+
public func update<T: Identifiable>(named: AliasKey<T>, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
241241
identityQueue.sync(flags: .barrier) {
242242
guard let entity = refAliases[named].value else {
243243
return false
@@ -259,7 +259,7 @@ extension IdentityMap {
259259
/// the change was applied
260260
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
261261
@discardableResult
262-
public func update<T: Aggregate>(named: AliasKey<T>, modifiedAt: Stamp = Date().stamp, update: Update<T>) -> Bool {
262+
public func update<T: Aggregate>(named: AliasKey<T>, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
263263
identityQueue.sync(flags: .barrier) {
264264
guard let entity = refAliases[named].value else {
265265
return false
@@ -281,7 +281,8 @@ extension IdentityMap {
281281
/// the change was applied
282282
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
283283
@discardableResult
284-
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp = Date().stamp, update: Update<[C.Element]>) -> Bool where C.Element: Identifiable {
284+
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<[C.Element]>)
285+
-> Bool where C.Element: Identifiable {
285286
identityQueue.sync(flags: .barrier) {
286287
guard let entities = refAliases[named].value else {
287288
return false
@@ -304,7 +305,8 @@ extension IdentityMap {
304305
/// the change was applied
305306
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
306307
@discardableResult
307-
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp = Date().stamp, update: Update<[C.Element]>) -> Bool where C.Element: Aggregate {
308+
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<[C.Element]>)
309+
-> Bool where C.Element: Aggregate {
308310
identityQueue.sync(flags: .barrier) {
309311
guard let entities = refAliases[named].value else {
310312
return false
@@ -343,8 +345,8 @@ extension IdentityMap {
343345
refAliases.removeAll()
344346
}
345347

346-
/// Removes all alias AND all objects stored weakly. You should not need this method and rather use `removeAlias`. But this can be useful
347-
/// if you fear retain cycles
348+
/// Removes all alias AND all objects stored weakly. You should not need this method and rather use `removeAlias`.
349+
/// But this can be useful if you fear retain cycles
348350
public func removeAll() {
349351
refAliases.removeAll()
350352
storage.removeAll()

Sources/CohesionKit/KeyPath/PartialIdentifiableKeyPath.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Combine
33
/// A `KeyPath` wrapper allowing only `Identifiable`/`Aggregate` keypaths
44
public struct PartialIdentifiableKeyPath<Root> {
55
let keyPath: PartialKeyPath<Root>
6-
let accept: (EntityNode<Root>, Root, Stamp, NestedEntitiesVisitor) -> Void
6+
let accept: (EntityNode<Root>, Root, Stamp?, NestedEntitiesVisitor) -> Void
77

88
/// Creates an instance referencing an `Identifiable` keyPath
99
public init<T: Identifiable>(_ keyPath: WritableKeyPath<Root, T>) {

Sources/CohesionKit/Storage/EntityNode.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ class EntityNode<T>: AnyEntityNode {
3737
self.init(ref: Observable(value: entity), modifiedAt: modifiedAt)
3838
}
3939

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

48-
modifiedAt = newModifiedAt
48+
modifiedAt = newModifiedAt ?? modifiedAt
4949
ref.value = newEntity
5050
}
5151

Sources/CohesionKit/Visitor/EntityContext.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import Foundation
44
struct EntityContext<Root, Value> {
55
let parent: EntityNode<Root>
66
let keyPath: WritableKeyPath<Root, Value>
7-
let stamp: Stamp
7+
let stamp: Stamp?
88
}

Tests/CohesionKitTests/Storage/EntityNodeTests.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,34 @@ class EntityNodeTests: XCTestCase {
4747
XCTAssertEqual(node.value as? RootFixture, startEntity)
4848
}
4949

50+
func test_updateEntity_stampIsNil_entityIsUpdated() throws {
51+
try node.updateEntity(newEntity, modifiedAt: nil)
52+
53+
XCTAssertEqual(node.value as? RootFixture, newEntity)
54+
}
55+
56+
func test_updateEntity_stampIsNil_stampIsNotUpdated() throws {
57+
let badEntity = RootFixture(id: 1, primitive: "wrong update", singleNode: .init(id: 1), listNodes: [])
58+
59+
try node.updateEntity(newEntity, modifiedAt: nil)
60+
61+
XCTAssertThrowsError(try node.updateEntity(badEntity, modifiedAt: startTimestamp - 1)) { error in
62+
switch error {
63+
case StampError.tooOld(let current, _):
64+
XCTAssertEqual(current, startTimestamp)
65+
default:
66+
XCTFail("Wrong error thrown")
67+
}
68+
}
69+
}
70+
5071
func test_observeChild_childChange_entityIsUpdated() throws {
51-
let childNode = EntityNode(startEntity.singleNode, modifiedAt: 0)
72+
let childNode = EntityNode(startEntity.singleNode, modifiedAt: nil)
5273
let newChild = SingleNodeFixture(id: 1, primitive: "updated")
5374

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

56-
try childNode.updateEntity(newChild, modifiedAt: 1)
77+
try childNode.updateEntity(newChild, modifiedAt: nil)
5778

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

74-
try childNode.updateEntity(newChild, modifiedAt: startTimestamp + 1)
95+
try childNode.updateEntity(newChild, modifiedAt: nil)
7596

7697
subscription.unsubscribe()
7798

0 commit comments

Comments
 (0)