Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Sources/Cache/Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ actor Cache {
return encoded
}

func resultTree(queryId: String) -> ResultTree? {
func resultTree(queryId: String, allowStale: Bool = false) -> ResultTree? {
// result trees are stored dehydrated in the cache
// retrieve cache, hydrate it and then return
guard let cacheProvider else {
Expand All @@ -101,6 +101,10 @@ actor Cache {
return nil
}

guard !dehydratedTree.isStale || allowStale else {
return nil
}

do {
let resultsProcessor = ResultTreeProcessor()
let (hydratedResults, rootObj) = try resultsProcessor.hydrateResults(
Expand All @@ -112,6 +116,7 @@ actor Cache {
data: hydratedResults,
cachedAt: dehydratedTree.cachedAt,
lastAccessed: dehydratedTree.lastAccessed,
maxAge: dehydratedTree.maxAge,
rootObject: rootObj
)

Expand Down Expand Up @@ -142,13 +147,15 @@ actor Cache {
[:],
cacheProvider: cacheProvider)

let maxAge = response.extensions?.maxAge ?? config.maxAge
cacheProvider
.setResultTree(
queryId: queryId,
tree: .init(
data: dehydratedResults,
cachedAt: Date(),
lastAccessed: Date(),
maxAge: maxAge,
rootObject: rootObj
)
)
Expand Down
8 changes: 6 additions & 2 deletions Sources/Cache/ResultTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,20 @@ struct ResultTree: Codable {
// Local time when the entry was read or updated
var lastAccessed: Date

// Validity for the results
var maxAge: TimeInterval

var rootObject: EntityNode?

func isStale(_ ttl: TimeInterval) -> Bool {
var isStale: Bool {
let now = Date()
return now.timeIntervalSince(cachedAt) > ttl
return now.timeIntervalSince(cachedAt) > maxAge
}

enum CodingKeys: String, CodingKey {
case cachedAt = "ca" // cached at
case lastAccessed = "la" // last accessed
case data = "d" // data cached
case maxAge = "ma" // max age
}
}
23 changes: 1 addition & 22 deletions Sources/Queries/GenericQueryRef.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,6 @@ actor GenericQueryRef<ResultData: Decodable & Sendable, Variable: OperationVaria

private let cache: Cache?

// maxAge received from server in query response
private var serverMaxAge: TimeInterval? = nil

// maxAge computed based on server or cache settings
// server is given priority over cache settings
private var maxAge: TimeInterval {
if let serverMaxAge {
DataConnectLogger.debug("Using maxAge specified from server \(serverMaxAge)")
return serverMaxAge
}

if let ma = cache?.config.maxAge {
DataConnectLogger.debug("Using maxAge specified from cache settings \(ma)")
return ma
}

return 0
}

// Ideally we would like this to be part of the QueryRef protocol
// Not adding for now since the protocol is Public
// This property is for now an internal property.
Expand Down Expand Up @@ -111,7 +92,6 @@ actor GenericQueryRef<ResultData: Decodable & Sendable, Variable: OperationVaria

do {
if let cache {
serverMaxAge = response.extensions?.maxAge
await cache.update(queryId: operationId, response: response, requestor: self)
}
}
Expand All @@ -136,8 +116,7 @@ actor GenericQueryRef<ResultData: Decodable & Sendable, Variable: OperationVaria
return OperationResult(data: nil, source: .cache)
}

if let cacheEntry = await cache.resultTree(queryId: request.requestId),
(cacheEntry.isStale(maxAge) && allowStale) || !cacheEntry.isStale(maxAge) {
if let cacheEntry = await cache.resultTree(queryId: request.requestId, allowStale: allowStale) {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(
ResultData.self,
Expand Down
7 changes: 4 additions & 3 deletions Tests/Unit/CacheTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -483,16 +483,17 @@ final class CacheTests: XCTestCase {
let resultTree = ResultTree(
data: resultTreeData.data(using: .utf8)!,
cachedAt: Date(),
lastAccessed: Date()
lastAccessed: Date(),
maxAge: ttl
)

// Within TTL
XCTAssertFalse(resultTree.isStale(ttl))
XCTAssertFalse(resultTree.isStale)

// Outside TTL
let expectation = XCTestExpectation(description: "Wait for TTL to expire")
DispatchQueue.main.asyncAfter(deadline: .now() + ttl + 0.1) {
XCTAssertTrue(resultTree.isStale(ttl))
XCTAssertTrue(resultTree.isStale)
expectation.fulfill()
}

Expand Down
Loading