Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to directly update the Apollo cache using write methods #413

Merged
merged 12 commits into from
Jan 27, 2019
Merged
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "stephencelis/SQLite.swift" ~> 0.11.5
github "daltoniam/Starscream" ~> 3.0.5
github "daltoniam/Starscream" ~> 3.0.6
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "daltoniam/Starscream" "3.0.5"
github "daltoniam/Starscream" "3.0.6"
github "stephencelis/SQLite.swift" "0.11.5"
2 changes: 1 addition & 1 deletion Carthage/Checkouts/Starscream
Submodule Starscream updated 50 files
+93 −2 .gitignore
+0 −1 .swift-version
+7 −0 .travis.yml
+4 −0 Gemfile
+201 −0 Gemfile.lock
+8 −0 Package.resolved
+9 −7 Package.swift
+7 −1 README.md
+1 −1 Sources/Starscream/Compression.swift
+92 −0 Sources/Starscream/SSLClientCertificate.swift
+5 −5 Sources/Starscream/SSLSecurity.swift
+62 −48 Sources/Starscream/WebSocket.swift
+3 −8 Starscream.podspec
+31 −42 Starscream.xcodeproj/project.pbxproj
+3 −1 Tests/CompressionTests.swift
+6 −0 build.sh
+11 −0 examples/WebSocketsOrgEcho/Podfile
+16 −0 examples/WebSocketsOrgEcho/Podfile.lock
+24 −0 examples/WebSocketsOrgEcho/Pods/Local Podspecs/Starscream.podspec.json
+16 −0 examples/WebSocketsOrgEcho/Pods/Manifest.lock
+26 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho-Info.plist
+182 −0 ...tsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho-acknowledgements.markdown
+5 −0 ...cketsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho-acknowledgements.plist
+5 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho-dummy.m
+158 −0 ...les/WebSocketsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho-frameworks.sh
+16 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho-umbrella.h
+11 −0 ...es/WebSocketsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho.debug.xcconfig
+6 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho.modulemap
+11 −0 .../WebSocketsOrgEcho/Pods/Target Support Files/Pods-WebSocketsOrgEcho/Pods-WebSocketsOrgEcho.release.xcconfig
+26 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Starscream/Starscream-Info.plist
+5 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Starscream/Starscream-dummy.m
+12 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Starscream/Starscream-prefix.pch
+16 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Starscream/Starscream-umbrella.h
+6 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Starscream/Starscream.modulemap
+9 −0 examples/WebSocketsOrgEcho/Pods/Target Support Files/Starscream/Starscream.xcconfig
+10 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho.xcworkspace/contents.xcworkspacedata
+8 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+21 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho/AppDelegate.swift
+98 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho/Assets.xcassets/AppIcon.appiconset/Contents.json
+6 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho/Assets.xcassets/Contents.json
+25 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho/Base.lproj/LaunchScreen.storyboard
+40 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho/Base.lproj/Main.storyboard
+45 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho/Info.plist
+19 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho/URL+Extensions.swift
+42 −0 examples/WebSocketsOrgEcho/WebSocketsOrgEcho/ViewController.swift
+28 −0 fastlane/Fastfile
+29 −0 fastlane/README.md
+4 −0 release.sh
+0 −2 zlib/include.h
+0 −9 zlib/module.modulemap
5 changes: 3 additions & 2 deletions Sources/Apollo/ApolloClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ public typealias OperationResultHandler<Operation: GraphQLOperation> = (_ result

/// The `ApolloClient` class provides the core API for Apollo. This API provides methods to fetch and watch queries, and to perform mutations.
public class ApolloClient {
let networkTransport: NetworkTransport
let store: ApolloStore
public let networkTransport: NetworkTransport
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to allow users to change these objects after client creation. Could we make this private(set) public instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing – I had just set them all to public for quick testing.

public let store: ApolloStore

public var cacheKeyForObject: CacheKeyForObject? {
get {
return store.cacheKeyForObject
Expand Down
18 changes: 10 additions & 8 deletions Sources/Apollo/ApolloStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public final class ApolloStore {
}
}

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

func load<Query: GraphQLQuery>(query: Query, resultHandler: @escaping OperationResultHandler<Query>) {
public func load<Query: GraphQLQuery>(query: Query, resultHandler: @escaping OperationResultHandler<Query>) {
load(query: query).andThen { result in
resultHandler(result, nil)
}.catch { error in
Expand All @@ -143,9 +143,10 @@ public final class ApolloStore {

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

fileprivate func makeExecutor() -> GraphQLExecutor {
fileprivate func makeExecutor(keySelector: @escaping (GraphQLResolveInfo) -> String = { $0.cacheKeyForField }) -> GraphQLExecutor {
let executor = GraphQLExecutor { object, info in
let value = object[info.cacheKeyForField]
let key = keySelector(info)
let value = object[key]
return self.complete(value: value)
}

Expand Down Expand Up @@ -206,8 +207,8 @@ public final class ApolloStore {
fileprivate var updateChangedKeysFunc: DidChangeKeysFunc?

init(cache: NormalizedCache, cacheKeyForObject: CacheKeyForObject?, updateChangedKeysFunc: @escaping DidChangeKeysFunc) {
self.updateChangedKeysFunc = updateChangedKeysFunc
super.init(cache: cache, cacheKeyForObject: cacheKeyForObject)
self.updateChangedKeysFunc = updateChangedKeysFunc
super.init(cache: cache, cacheKeyForObject: cacheKeyForObject)
}

public func update<Query: GraphQLQuery>(query: Query, _ body: (inout Query.Data) throws -> Void) throws {
Expand All @@ -232,12 +233,13 @@ public final class ApolloStore {

private func write(object: JSONObject, forSelections selections: [GraphQLSelection], withKey key: CacheKey, variables: GraphQLMap?) throws {
let normalizer = GraphQLResultNormalizer()
try self.makeExecutor().execute(selections: selections, on: object, withKey: key, variables: variables, accumulator: normalizer)
let executor = makeExecutor { $0.responseKeyForField }
try executor.execute(selections: selections, on: object, withKey: key, variables: variables, accumulator: normalizer)
.flatMap {
self.cache.merge(records: $0)
}.andThen { changedKeys in
if let didChangeKeysFunc = self.updateChangedKeysFunc {
didChangeKeysFunc(changedKeys, nil)
didChangeKeysFunc(changedKeys, nil)
}
}.wait()
}
Expand Down
41 changes: 39 additions & 2 deletions Tests/ApolloCacheDependentTests/ReadWriteFromStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ReadWriteFromStoreTests: XCTestCase {
})
}
}

func testReadHeroNameQueryWithMissingName() throws {
let initialRecords: RecordSet = [
"QUERY_ROOT": ["hero": Reference(key: "hero")],
Expand Down Expand Up @@ -160,7 +160,44 @@ class ReadWriteFromStoreTests: XCTestCase {
XCTAssertEqual(friendsNames, ["Luke Skywalker", "Han Solo", "Leia Organa", "C-3PO"])
}
}


func testUpdateHeroAndFriendsNamesQueryWithVariable() throws {
let initialRecords: RecordSet = [
"QUERY_ROOT": ["hero(episode:NEWHOPE)": Reference(key: "2001")],
"2001": [
"name": "R2-D2",
"__typename": "Droid",
"friends": [
Reference(key: "1000"),
Reference(key: "1002"),
Reference(key: "1003")
]
],
"1000": ["__typename": "Human", "name": "Luke Skywalker"],
"1002": ["__typename": "Human", "name": "Han Solo"],
"1003": ["__typename": "Human", "name": "Leia Organa"],
]

try withCache(initialRecords: initialRecords) { (cache) in
let store = ApolloStore(cache: cache)

let query = HeroAndFriendsNamesQuery(episode: Episode.newhope)

try await(store.withinReadWriteTransaction { transaction in
try transaction.update(query: query) { (data: inout HeroAndFriendsNamesQuery.Data) in
data.hero?.friends?.append(.makeDroid(name: "C-3PO"))
}
})

let result = try await(store.load(query: query))
guard let data = result.data else { XCTFail(); return }

XCTAssertEqual(data.hero?.name, "R2-D2")
let friendsNames = data.hero?.friends?.compactMap { $0?.name }
XCTAssertEqual(friendsNames, ["Luke Skywalker", "Han Solo", "Leia Organa", "C-3PO"])
}
}

func testReadHeroDetailsFragmentWithTypeSpecificProperty() throws {
let initialRecords: RecordSet = [
"2001": ["name": "R2-D2", "__typename": "Droid", "primaryFunction": "Protocol"]
Expand Down