Skip to content

Commit b4db31a

Browse files
Nickersoftmartijnwalraven
authored andcommitted
Added ability to directly update the Apollo cache using write methods (#413)
1 parent f095ec2 commit b4db31a

8 files changed

Lines changed: 82 additions & 25 deletions

File tree

Cartfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
github "stephencelis/SQLite.swift" ~> 0.11.5
2-
github "daltoniam/Starscream" ~> 3.0.5
2+
github "daltoniam/Starscream" ~> 3.0.6

Cartfile.resolved

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
github "daltoniam/Starscream" "3.0.5"
1+
github "daltoniam/Starscream" "3.0.6"
22
github "stephencelis/SQLite.swift" "0.11.5"

Carthage/Checkouts/Starscream

Submodule Starscream updated 50 files

Sources/Apollo/ApolloClient.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ public typealias OperationResultHandler<Operation: GraphQLOperation> = (_ result
2929
/// The `ApolloClient` class provides the core API for Apollo. This API provides methods to fetch and watch queries, and to perform mutations.
3030
public class ApolloClient {
3131
let networkTransport: NetworkTransport
32-
let store: ApolloStore
32+
33+
public let store: ApolloStore
34+
3335
public var cacheKeyForObject: CacheKeyForObject? {
3436
get {
3537
return store.cacheKeyForObject
@@ -39,7 +41,7 @@ public class ApolloClient {
3941
store.cacheKeyForObject = newValue
4042
}
4143
}
42-
44+
4345
private let queue: DispatchQueue
4446
private let operationQueue: OperationQueue
4547

Sources/Apollo/ApolloStore.swift

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public final class ApolloStore {
118118
}
119119
}
120120

121-
func load<Query: GraphQLQuery>(query: Query) -> Promise<GraphQLResult<Query.Data>> {
121+
public func load<Query: GraphQLQuery>(query: Query) -> Promise<GraphQLResult<Query.Data>> {
122122
return withinReadTransaction { transaction in
123123
let mapper = GraphQLSelectionSetMapper<Query.Data>()
124124
let dependencyTracker = GraphQLDependencyTracker()
@@ -129,7 +129,7 @@ public final class ApolloStore {
129129
}
130130
}
131131

132-
func load<Query: GraphQLQuery>(query: Query, resultHandler: @escaping OperationResultHandler<Query>) {
132+
public func load<Query: GraphQLQuery>(query: Query, resultHandler: @escaping OperationResultHandler<Query>) {
133133
load(query: query).andThen { result in
134134
resultHandler(result, nil)
135135
}.catch { error in
@@ -143,17 +143,6 @@ public final class ApolloStore {
143143

144144
fileprivate lazy var loader: DataLoader<CacheKey, Record?> = DataLoader(self.cache.loadRecords)
145145

146-
fileprivate func makeExecutor() -> GraphQLExecutor {
147-
let executor = GraphQLExecutor { object, info in
148-
let value = object[info.cacheKeyForField]
149-
return self.complete(value: value)
150-
}
151-
152-
executor.dispatchDataLoads = self.loader.dispatch
153-
executor.cacheKeyForObject = self.cacheKeyForObject
154-
return executor
155-
}
156-
157146
init(cache: NormalizedCache, cacheKeyForObject: CacheKeyForObject?) {
158147
self.cache = cache
159148
self.cacheKeyForObject = cacheKeyForObject
@@ -187,7 +176,16 @@ public final class ApolloStore {
187176

188177
final func execute<Accumulator: GraphQLResultAccumulator>(selections: [GraphQLSelection], onObjectWithKey key: CacheKey, variables: GraphQLMap?, accumulator: Accumulator) throws -> Promise<Accumulator.FinalResult> {
189178
return loadObject(forKey: key).flatMap { object in
190-
try self.makeExecutor().execute(selections: selections, on: object, withKey: key, variables: variables, accumulator: accumulator)
179+
let executor = GraphQLExecutor { object, info in
180+
let value = object[info.cacheKeyForField]
181+
return self.complete(value: value)
182+
}
183+
184+
185+
executor.dispatchDataLoads = self.loader.dispatch
186+
executor.cacheKeyForObject = self.cacheKeyForObject
187+
188+
return try executor.execute(selections: selections, on: object, withKey: key, variables: variables, accumulator: accumulator)
191189
}
192190
}
193191

@@ -206,8 +204,8 @@ public final class ApolloStore {
206204
fileprivate var updateChangedKeysFunc: DidChangeKeysFunc?
207205

208206
init(cache: NormalizedCache, cacheKeyForObject: CacheKeyForObject?, updateChangedKeysFunc: @escaping DidChangeKeysFunc) {
209-
self.updateChangedKeysFunc = updateChangedKeysFunc
210-
super.init(cache: cache, cacheKeyForObject: cacheKeyForObject)
207+
self.updateChangedKeysFunc = updateChangedKeysFunc
208+
super.init(cache: cache, cacheKeyForObject: cacheKeyForObject)
211209
}
212210

213211
public func update<Query: GraphQLQuery>(query: Query, _ body: (inout Query.Data) throws -> Void) throws {
@@ -232,12 +230,18 @@ public final class ApolloStore {
232230

233231
private func write(object: JSONObject, forSelections selections: [GraphQLSelection], withKey key: CacheKey, variables: GraphQLMap?) throws {
234232
let normalizer = GraphQLResultNormalizer()
235-
try self.makeExecutor().execute(selections: selections, on: object, withKey: key, variables: variables, accumulator: normalizer)
233+
let executor = GraphQLExecutor { object, info in
234+
return .result(.success(object[info.responseKeyForField]))
235+
}
236+
237+
executor.cacheKeyForObject = self.cacheKeyForObject
238+
239+
try executor.execute(selections: selections, on: object, withKey: key, variables: variables, accumulator: normalizer)
236240
.flatMap {
237241
self.cache.merge(records: $0)
238242
}.andThen { changedKeys in
239243
if let didChangeKeysFunc = self.updateChangedKeysFunc {
240-
didChangeKeysFunc(changedKeys, nil)
244+
didChangeKeysFunc(changedKeys, nil)
241245
}
242246
}.wait()
243247
}

Tests/ApolloCacheDependentTests/ReadWriteFromStoreTests.swift

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class ReadWriteFromStoreTests: XCTestCase {
4343
})
4444
}
4545
}
46-
46+
4747
func testReadHeroNameQueryWithMissingName() throws {
4848
let initialRecords: RecordSet = [
4949
"QUERY_ROOT": ["hero": Reference(key: "hero")],
@@ -160,7 +160,44 @@ class ReadWriteFromStoreTests: XCTestCase {
160160
XCTAssertEqual(friendsNames, ["Luke Skywalker", "Han Solo", "Leia Organa", "C-3PO"])
161161
}
162162
}
163-
163+
164+
func testUpdateHeroAndFriendsNamesQueryWithVariable() throws {
165+
let initialRecords: RecordSet = [
166+
"QUERY_ROOT": ["hero(episode:NEWHOPE)": Reference(key: "2001")],
167+
"2001": [
168+
"name": "R2-D2",
169+
"__typename": "Droid",
170+
"friends": [
171+
Reference(key: "1000"),
172+
Reference(key: "1002"),
173+
Reference(key: "1003")
174+
]
175+
],
176+
"1000": ["__typename": "Human", "name": "Luke Skywalker"],
177+
"1002": ["__typename": "Human", "name": "Han Solo"],
178+
"1003": ["__typename": "Human", "name": "Leia Organa"],
179+
]
180+
181+
try withCache(initialRecords: initialRecords) { (cache) in
182+
let store = ApolloStore(cache: cache)
183+
184+
let query = HeroAndFriendsNamesQuery(episode: Episode.newhope)
185+
186+
try await(store.withinReadWriteTransaction { transaction in
187+
try transaction.update(query: query) { (data: inout HeroAndFriendsNamesQuery.Data) in
188+
data.hero?.friends?.append(.makeDroid(name: "C-3PO"))
189+
}
190+
})
191+
192+
let result = try await(store.load(query: query))
193+
guard let data = result.data else { XCTFail(); return }
194+
195+
XCTAssertEqual(data.hero?.name, "R2-D2")
196+
let friendsNames = data.hero?.friends?.compactMap { $0?.name }
197+
XCTAssertEqual(friendsNames, ["Luke Skywalker", "Han Solo", "Leia Organa", "C-3PO"])
198+
}
199+
}
200+
164201
func testReadHeroDetailsFragmentWithTypeSpecificProperty() throws {
165202
let initialRecords: RecordSet = [
166203
"2001": ["name": "R2-D2", "__typename": "Droid", "primaryFunction": "Protocol"]

Tests/ApolloPerformanceTests/NormalizedCachingTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,22 @@ import XCTest
33
import StarWarsAPI
44

55
private final class MockBatchedNormalizedCache: NormalizedCache {
6+
67
private var records: RecordSet
78

89
init(records: RecordSet) {
910
self.records = records
1011
}
1112

13+
func clear() -> Promise<Void> {
14+
return Promise { fulfill, reject in
15+
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(100)) {
16+
self.records.clear()
17+
fulfill(())
18+
}
19+
}
20+
}
21+
1222
func loadRecords(forKeys keys: [CacheKey]) -> Promise<[Record?]> {
1323
return Promise { fulfill, reject in
1424
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(100)) {

Tests/ApolloWebsocketTests/MockWebSocket.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import Starscream
22
@testable import ApolloWebSocket
33

44
class MockWebSocket: ApolloWebSocketClient {
5+
var pongDelegate: WebSocketPongDelegate?
6+
7+
var sslClientCertificate: SSLClientCertificate?
8+
59
required init(request: URLRequest, protocols: [String]?) {
610
}
711

0 commit comments

Comments
 (0)