Skip to content

feat(hooks): honor Cache-Control lifetime for Bible reads - #371

Open
cameronapak wants to merge 10 commits into
mainfrom
cursor/cp/ype-5453-cache-control-lifetime-d890
Open

feat(hooks): honor Cache-Control lifetime for Bible reads#371
cameronapak wants to merge 10 commits into
mainfrom
cursor/cp/ype-5453-cache-control-lifetime-d890

Conversation

@cameronapak

@cameronapak cameronapak commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Implements YPE-5453: TanStack Query uses remaining Cache-Control lifetime (max-age minus Age) for Bible reads under /v1/bibles/{id}.

Closes YPE-5453. Does not close YPE-5262 (RN Expo disk cache).

What changes

  • Parse Cache-Control / Age the same way Swift and Kotlin do (7-day fallback, Age 0 when missing, no-cache/no-store skip reuse on remount).
  • GET stays body-only. ApiClient.getWithPolicy returns { data, policy }.
  • One hook-facing BibleClient.readWithPolicy (discriminated BibleCacheRead). Partners still use the body-only getters. There are no public get*WithPolicy twins.
  • Opted-in hooks pass useApiData(key, fetchFn, { cacheControl: true }) so fetchFn is typed to return { data, policy }. Bare T stays default-fresh (versions list, highlights, VOTD). No duck-typing of envelopes.
  • After a successful opted-in read, remainingMs is written as that query's staleTime and gcTime (setQueryDefaults + live query.setOptions).
  • Cache stays memory-only and per YouVersionProvider. A mounted chapter may stay on screen after expiry; a remount after expiry fetches again.
  • ADR 0006 amended.

Review follow-up

Simplifier asked not to ship the eight public with-policy methods or a guessed { data, policy } envelope. This revision keeps parseCachePolicy, getWithPolicy, and applyCacheLifetime, and switches opt-in to the explicit cacheControl flag plus one internal Bible read.

Verification

  • Unit: parseCachePolicy table matches Kotlin/Swift cases (quoted max-age, decimals).
  • Unit: GET regression plus getWithPolicy 2xx / non-2xx. readWithPolicy returns version + policy.
  • Hook: remount inside lifetime is a cache hit; remount after remainingMs fetches; remount after the default five-minute unused window still hits when remainingMs is longer; mounted observer keeps data past expiry; refetch() fetches.
  • Hook: a { data, policy } payload stays data when cacheControl is omitted.
  • Hook: versions-list remount still refetches.

Manual network-panel check needs a live VITE_YVP_APP_KEY (not in this environment).

Out of scope

Disk cache, ETag/304, Expires/Date, sharing one query cache across provider instances or Expo WebViews.

Open in Web Open in Cursor 

Greptile Summary

The PR completes the previously missing hooks integration, allowing opted-in Bible reads to use the API’s remaining Cache-Control lifetime while preserving body-only core getters.

  • Adds Cache-Control and Age parsing with a seven-day fallback.
  • Adds policy-aware Bible reads and applies their lifetime to TanStack Query’s live query and defaults.
  • Keeps highlights, the versions list, and VOTD on their existing freshness behavior.
  • Adds coverage for parsing, transport envelopes, remounts, expiry, garbage collection, and SSR.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported mismatch between the changeset and hooks implementation has been resolved.

Important Files Changed

Filename Overview
packages/core/src/parse-cache-policy.ts Parses response cache directives into a bounded remaining lifetime with explicit fallback and non-cacheable behavior.
packages/core/src/client.ts Adds a policy-aware GET path while preserving the existing body-only request contract.
packages/core/src/bible.ts Adds typed policy-aware Bible reads and keeps existing public getters body-only.
packages/hooks/src/useApiData.ts Unwraps opted-in policy envelopes and applies the response lifetime to both query defaults and the live query.
.changeset/bible-cache-control-lifetime.md Accurately describes the now-complete core and hooks cache-lifetime behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Hook[Bible hook] --> Read[BibleClient.readWithPolicy]
  Read --> API[ApiClient.getWithPolicy]
  API --> Policy[Parse Cache-Control and Age]
  Policy --> Envelope["{ data, policy }"]
  Envelope --> Query[useApiData]
  Query --> Lifetime[Apply staleTime and gcTime]
  Lifetime --> Cache[Provider-local memory cache]
Loading

Reviews (3): Last reviewed commit: "refactor: type cache-control opt-in and ..." | Re-trigger Greptile

Context used:

YPE-5453. Release notes for the in-memory TanStack lifetime work.

Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1049a3a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@youversion/platform-core Minor
@youversion/platform-react-hooks Minor
@youversion/platform-react-ui Minor
vite-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Add parseCachePolicy so Bible reads can honor remaining max-age minus Age.
YPE-5453. Native policy trims quotes around the max-age value.
@cameronapak cameronapak self-assigned this Sep 1, 2026
@cursor
cursor Bot force-pushed the cursor/cp/ype-5453-cache-control-lifetime-d890 branch from 240827a to dbbe965 Compare September 1, 2026 14:20
@cameronapak
cameronapak marked this pull request as ready for review September 1, 2026 14:25
@cameronapak
cameronapak marked this pull request as draft September 1, 2026 14:26
Comment thread .changeset/bible-cache-control-lifetime.md
Unwrap `{ data, policy }` envelopes in useApiData and set that query's
staleTime and gcTime from remainingMs so remounts in the same provider
honor Cache-Control without changing the public hook surface.
setQueryDefaults alone left the first fetch on the five-minute
gcTime. Remaining lifetime now updates that query instance too.
@cameronapak
cameronapak marked this pull request as ready for review September 1, 2026 15:00
Anonymous object types on the remount `latest` bindings tripped
the anti-slop rule. Use the same named probe contract as useApiData.
@cameronapak

cameronapak commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

Review

Jira ticket: YPE-5453

Summary

Simpler: yes. Worst: eight public with-policy Bible methods and an envelope that we guess from fields.

Pre-flight: DO NOT SHIP.

For Agents

Healthy pin: main e580502 vs HEAD 3fa6083.
Settled: Greptile P1 getWithPolicy unused — Cam resolved. Do not reopen.
Simpler shape: keep parseCachePolicy, ApiClient.getWithPolicy, GET body-only, applyCacheLifetime (setQueryDefaults + query.setOptions). Delete exported get*WithPolicy twins and WithPolicy. Do not duck-type envelopes. useApiData(key, fetchFn, { cacheControl: true }) with fetchFn returning { data, policy }. One internal Bible read, not eight public methods. Do not use static 7-day staleTime.

By Simplifier bot, sent on behalf of Cam.

Comment thread packages/core/src/bible.ts Outdated
Comment thread packages/hooks/src/useApiData.ts Outdated
useApiData now takes { cacheControl: true } so the fetch is typed as
{ data, policy } instead of guessing an envelope from its fields. Bible
hooks use one readWithPolicy; partners still get body-only getters.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants