From 205cf2687ce48f3d004792ff223422a86cc6bdbc Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 6 May 2026 20:41:49 -0500 Subject: feat(cache): persist read-only API responses 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. --- Hutch/Networking/ProjectService.swift | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'Hutch/Networking/ProjectService.swift') diff --git a/Hutch/Networking/ProjectService.swift b/Hutch/Networking/ProjectService.swift index 5e0e061..44320ca 100644 --- a/Hutch/Networking/ProjectService.swift +++ b/Hutch/Networking/ProjectService.swift @@ -287,12 +287,17 @@ struct ProjectService: Sendable { variables["cursor"] = cursor } - let response = try await client.execute( + let cached = try await client.executeCached( service: .hub, query: Self.projectsQuery, variables: variables.isEmpty ? nil : variables, - responseType: ProjectPageResponse.self + responseType: ProjectPageResponse.self, + cacheKey: APICacheKeys.projects(cursor: cursor), + resourceType: .userProfile, + ttl: APICacheTTLs.projectList, + policy: .cacheFirstThenRefresh ) + let response = cached.value results.append(contentsOf: response.me.projects.results) guard let nextCursor = response.me.projects.cursor else { @@ -324,12 +329,22 @@ struct ProjectService: Sendable { variables["trackersCursor"] = trackersCursor } - let response = try await client.execute( + let cached = try await client.executeCached( service: .hub, query: Self.projectDetailQuery, variables: variables, - responseType: ProjectDetailResponse.self + responseType: ProjectDetailResponse.self, + cacheKey: APICacheKeys.projectDetail( + rid: rid, + mailingListsCursor: mailingListsCursor, + sourcesCursor: sourcesCursor, + trackersCursor: trackersCursor + ), + resourceType: .userProfile, + ttl: APICacheTTLs.projectDetail, + policy: .cacheFirstThenRefresh ) + let response = cached.value guard let project = response.project else { throw SRHTError.decodingError( -- cgit v1.2.3