summaryrefslogtreecommitdiff
path: root/Hutch/Views/Builds
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-05-06 20:41:49 -0500
committerChristian Cleberg <[email protected]>2026-05-06 20:41:49 -0500
commit205cf2687ce48f3d004792ff223422a86cc6bdbc (patch)
tree7edb78cede354e79aa8abd764ed9c5047e0ee168 /Hutch/Views/Builds
parent57e4f34b4613c09beb0cb757ac2ba2b43cc04daf (diff)
downloadhutch-205cf2687ce48f3d004792ff223422a86cc6bdbc.tar.gz
hutch-205cf2687ce48f3d004792ff223422a86cc6bdbc.tar.bz2
hutch-205cf2687ce48f3d004792ff223422a86cc6bdbc.zip
feat(cache): persist read-only API responsesv3.3.1
Add a bounded stale-while-revalidate cache at the Sourcehut API boundary with stable keys, centralized TTLs, request coalescing, payload hashing, and LRU disk pruning. Cache high-value read-only repo, build, ticket, project, profile, paste, and Home/Work Queue data while keeping mutations network-only and invalidating related prefixes after successful writes. Add focused cache tests and implementation notes.
Diffstat (limited to 'Hutch/Views/Builds')
-rw-r--r--Hutch/Views/Builds/BuildListViewModel.swift23
1 files changed, 17 insertions, 6 deletions
diff --git a/Hutch/Views/Builds/BuildListViewModel.swift b/Hutch/Views/Builds/BuildListViewModel.swift
index 14f50fd..42500a8 100644
--- a/Hutch/Views/Builds/BuildListViewModel.swift
+++ b/Hutch/Views/Builds/BuildListViewModel.swift
@@ -188,7 +188,7 @@ final class BuildListViewModel {
func loadJobs() async {
// Show cached data immediately on first load (may populate `jobs` from cache).
if jobs.isEmpty {
- loadFromCache()
+ await loadFromCache()
}
let treatAsInitialLoad = jobs.isEmpty
@@ -279,6 +279,7 @@ final class BuildListViewModel {
variables: variables,
responseType: SubmitJobResponse.self
)
+ await invalidateBuildListCache()
await loadJobs()
return result.submit.id
} catch {
@@ -297,6 +298,7 @@ final class BuildListViewModel {
variables: ["id": job.id],
responseType: CancelResponse.self
)
+ await invalidateBuildListCache()
if let index = jobs.firstIndex(where: { $0.id == job.id }) {
let updated = JobSummary(
id: job.id,
@@ -345,14 +347,17 @@ final class BuildListViewModel {
}
if useCache && cursor == nil {
- let result = try await client.executeAndCache(
+ let cached = try await client.executeCached(
service: .builds,
query: Self.query,
variables: variables.isEmpty ? nil : variables,
responseType: JobsResponse.self,
- cacheKey: Self.cacheKey
+ cacheKey: APICacheKeys.builds(cursor: cursor),
+ resourceType: .buildList,
+ ttl: APICacheTTLs.activeBuild,
+ policy: .cacheFirstThenRefresh
)
- return result.jobs
+ return cached.value.jobs
} else {
let result = try await client.execute(
service: .builds,
@@ -364,8 +369,14 @@ final class BuildListViewModel {
}
}
- private func loadFromCache() {
- guard let data = client.responseCache.get(forKey: Self.cacheKey) else { return }
+ private func invalidateBuildListCache() async {
+ await client.invalidateCache(prefix: APICacheKeys.prefix(SRHTService.builds.rawValue, "jobs"))
+ await client.invalidateCache(prefix: APICacheKeys.prefix(SRHTService.builds.rawValue, "job"))
+ await client.invalidateCache(prefix: APICacheKeys.prefix("home"))
+ }
+
+ private func loadFromCache() async {
+ guard let data = await client.cachedPayload(forKey: APICacheKeys.builds()) ?? client.responseCache.get(forKey: Self.cacheKey) else { return }
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .srhtFlexible
if let response = try? decoder.decode(