feat(workflows): add WorkflowsCache and disk persistence for workflows list#6881
Merged
Conversation
…s list First of three stacked PRs porting purchases-android#3508 (wire workflows fetching + cache persistence) to iOS. This PR adds the caching foundation only. - Add `WorkflowsCache`: in-memory cache for resolved per-workflow `WorkflowDataResult`s and the workflows list (plus its derived offeringId to workflowId map), with the same 5 min foreground / 25 hr background TTL as offerings. It also owns the disk copy of the list. - Add `DeviceCache` disk persistence for the workflows-list response (`cachedWorkflowsListResponse` / `cache(workflowsListResponse:)` / `clearWorkflowsListResponseCache`), following the offerings-response pattern. Single non-user-scoped key; cross-user safety handled by clearing on identity transitions (a later PR), mirroring offerings. - Expose an internal `DeviceCache.cacheDurationInSeconds(isAppBackgrounded:)` so other caches reuse the identical TTL policy. `WorkflowsCache` manages its own timestamps via an injected `DateProvider` (rather than `InMemoryCachedObject`, whose staleness is tied to the real wall clock) so cache-expiry is deterministically testable, mirroring Android. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
facumenzella
commented
Jun 2, 2026
facumenzella
commented
Jun 2, 2026
7c5ff4b to
51a9c85
Compare
…ache - Drop the "mirroring the Android SDK" mention from the doc comment. - Correct the `@unchecked Sendable` rationale now that the class is `final`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Member
Author
|
I'll simplify some comments because they are just too much 😆 |
tonidero
reviewed
Jun 2, 2026
tonidero
left a comment
Contributor
There was a problem hiding this comment.
Looking great! Left some small comments but nothing blocking. Will leave approval to someone with more context though 🙏
ajpallares
reviewed
Jun 2, 2026
ajpallares
left a comment
Member
There was a problem hiding this comment.
Looking good! Left some comments
- Remove dead deleteOldFileIfNeeded calls for the workflows-list key: the cache is brand new and never wrote to the pre-largeItemCache documents directory, so there is nothing to clean up there. - Correct the workflows-list scoping comment (iOS offerings disk cache is user-scoped, so the "just like offerings" framing was misleading). - Rename the offeringIdMap parameter/field to workflowIdByOfferingId so the map direction is unambiguous. - Trim verbose doc comments that documented current context rather than the code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d list Drop the passed-in map parameter from cache(workflowsList:) and derive the workflowId for an offering directly from WorkflowSummary.offeringId. workflowId( forOfferingId:) now reads from the in-memory list and falls back to the disk copy, so it resolves after a disk restore before the next fetch repopulates memory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Member
Author
|
Thank yo so much @tonidero @ajpallares would you mind doing another pass whenever you can? :) |
ajpallares
reviewed
Jun 2, 2026
Resolve offering->workflow ids from a precomputed in-memory map built when the list is cached, instead of falling back to disk on every lookup. The disk fallback re-ran a synchronized UserDefaults write plus a file read and JSON decode on each call without ever repopulating memory; restoring the persisted list into memory after a backend failure is the caller's job. Duplicate offeringIds keep last-wins resolution and now log a warning so the collision isn't silent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vegaro
approved these changes
Jun 2, 2026
vegaro
left a comment
Member
There was a problem hiding this comment.
some nits and tests. looks good after fixing
ajpallares
approved these changes
Jun 2, 2026
ajpallares
left a comment
Member
There was a problem hiding this comment.
Thank you for iterating on this! I think this looks good!
…ache-pr1 # Conflicts: # Sources/Logging/Strings/BackendErrorStrings.swift
Rename testCacheWorkflowsListPersistsResponseToDisk to reflect that it only verifies the response is forwarded to DeviceCache, and add backgrounded-TTL coverage for the workflows list cache. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ments Per review, keep the doc comments to what the methods do and drop the contextual notes about identity-transition races and caller hydration responsibility that can drift out of date. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The isSandbox parameter was always passed self.systemInfo.isSandbox, so fold it into cacheDurationInSeconds(isAppBackgrounded:) which reads it internally, and update the offerings staleness call sites. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Port of RevenueCat/purchases-android#3508 (1 of 3 stacked PRs).
Android #3508 wires up workflows fetching end-to-end and adds in-memory + disk caching. We already have the workflow networking layer on iOS (
WorkflowsAPI, thegetWorkflow/getWorkflowsendpoints, response models), so this port is really about the orchestration + caching + lifecycle we're missing. Splitting it into three stacked PRs:WorkflowManager+ list fetch + offeringId→workflowId resolutionWhat Changed
WorkflowsCache, owner of in-memory workflow state: resolved per-workflowWorkflowDataResults plus the workflows list and itsofferingId → workflowIdmap. Same 5 min / 25 hr foreground/background TTL as offerings. It also owns the disk copy of the list (persist on cache, restore on backend failure, wipe on clear), same asDeviceCachedoes for offerings.DeviceCachegets disk persistence for the workflows-list response (cachedWorkflowsListResponse/cache(workflowsListResponse:)/clearWorkflowsListResponseCache)Nothing calls this yet, it's just the foundation the next two PRs build on.
Notes
WorkflowsCachekeeps its own timestamps via an injectedDateProviderinstead of reusingInMemoryCachedObject(whose staleness is tied to the real wall clock) so the TTL is actually testable. Same as Android.pendingCompletionCallbacks/FetchDecision), ourCallbackCachealready dedups in-flight requests. PR 2 only needs the higher-level "fire once the list + prefetches are done" piece.Note
Low Risk
Self-contained caching with broad unit tests and no production wiring yet; the non-user-scoped workflows list key relies on future identity-transition clearing.
Overview
Adds a workflows caching foundation that is not wired into fetch/orchestration yet.
WorkflowsCacheholds in-memory per-workflowWorkflowDataResultentries and the workflows list, including anofferingId → workflowIdmap built when the list is cached. Staleness uses the same foreground/background TTL as offerings, driven by an injectableDateProvider(notInMemoryCachedObject) so TTL behavior is testable.clearCache()clears memory and the on-disk list.DeviceCachegains disk persistence forWorkflowsListResponseunder a single non-user-scoped key (workflowsListResponse), stored via large-item/file cache (not UserDefaults).cacheDurationInSecondsno longer takesisSandbox; callers pass only background state and sandbox is read fromsystemInfo.Duplicate
offeringIdentries in a workflows list log a backend warning; the last workflow wins for lookup.Unit tests cover
WorkflowsCacheTTL/staleness, list↔offering mapping, disk round-trip, andDeviceCacheworkflows-list caching.Reviewed by Cursor Bugbot for commit b528ba6. Bugbot is set up for automated code reviews on this repo. Configure here.