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/PasteService.swift | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'Hutch/Networking/PasteService.swift') diff --git a/Hutch/Networking/PasteService.swift b/Hutch/Networking/PasteService.swift index b9aa530..92a618e 100644 --- a/Hutch/Networking/PasteService.swift +++ b/Hutch/Networking/PasteService.swift @@ -109,13 +109,17 @@ final class PasteService: Sendable { let variables = cursor.map { ["cursor": $0 as any Sendable] } let result: PasteListResponse if useCache, cursor == nil { - result = try await client.executeAndCache( + let cached = try await client.executeCached( service: .paste, query: Self.listQuery, variables: variables, responseType: PasteListResponse.self, - cacheKey: Self.cacheKey + cacheKey: APICacheKeys.pasteList(cursor: cursor), + resourceType: .pasteList, + ttl: APICacheTTLs.ticketList, + policy: .cacheFirstThenRefresh ) + result = cached.value } else { result = try await client.execute( service: .paste, @@ -127,8 +131,8 @@ final class PasteService: Sendable { return result.pastes ?? PasteListPage(results: [], cursor: nil) } - func loadCachedPastes() -> PasteListPage? { - guard let data = client.responseCache.get(forKey: Self.cacheKey) else { + func loadCachedPastes() async -> PasteListPage? { + guard let data = await client.cachedPayload(forKey: APICacheKeys.pasteList()) ?? client.responseCache.get(forKey: Self.cacheKey) else { return nil } @@ -171,6 +175,7 @@ final class PasteService: Sendable { }, responseType: CreatePasteResponse.self ) + await invalidatePasteCaches() return result.create } @@ -181,6 +186,7 @@ final class PasteService: Sendable { variables: ["id": id, "visibility": visibility.rawValue], responseType: UpdatePasteResponse.self ) + await invalidatePasteCaches() return result.update } @@ -191,6 +197,7 @@ final class PasteService: Sendable { variables: ["id": id], responseType: DeletePasteResponse.self ) + await invalidatePasteCaches() return result.delete } @@ -208,6 +215,10 @@ final class PasteService: Sendable { return (draft.filename, data) } } + + private func invalidatePasteCaches() async { + await client.invalidateCache(prefix: APICacheKeys.prefix(SRHTService.paste.rawValue, "pastes")) + } } private struct PasteListResponse: Decodable, Sendable { -- cgit v1.2.3