Repository navigation
feat(cachekitio)!: 90-day TTL range; bound the writer's L1 copy by the PUT Fresh-For (LAB-12090) - #301
feat(cachekitio)!: 90-day TTL range; bound the writer's L1 copy by the PUT Fresh-For (LAB-12090)#30127Bslash6 wants to merge 5 commits into
Conversation
…PUT Fresh-For The protocol's TTL range is now 1..7,776,000 seconds (90 days), and each plan has a maximum TTL that the server clamps to rather than rejects. The SDK does not know the plan, so it sends any TTL in the range as is: X-CacheKit-TTL and the PATCH /ttl body accept 1..7,776,000, staleTtl <= 7,775,999 and ttl + staleTtl <= 7,776,000. The X-CacheKit-Fresh-For grammar cap stays 2,592,000 under its own constant. A clamped write is stored for less than the TTL it stated, so the writer's L1 copy must follow the bound the store applied (API-103). Backend.set and setWithStaleTtl may now resolve to a SetResult carrying the PUT 2xx's X-CacheKit-Fresh-For, read with the GET grammar. The cache caps the L1 copy at min(ttl, fresh_for) counted from receipt of the 2xx, on direct writes, on write-behind (once the background PUT lands) and on SWR refreshes; a malformed or 0 value keeps no copy; an absent header keeps today's lifetime. A write that degradation absorbed keeps its copy for the TTL, as before (API-36). PATCH /ttl responses never touch an L1 bound (API-104). Delegating Backend wrappers must return the inner set's result, as they already forward getWithTtl.
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Write result and TTL validation packages/cachekit/src/backends/types.ts, packages/cachekit/src/backends/cachekitio.ts, packages/cachekit/src/exports-common.ts, packages/cachekit/src/types/cache.ts, packages/cachekit/src/backends/cachekitio-ttl.test.ts, packages/cachekit/src/backends/cachekitio.test.ts, packages/cachekit/src/cache.stale-grace.test.ts, packages/cachekit/README.md, .secrets.baseline |
Backend writes can return SetResult with an optional freshFor value. CachekitIO parses successful PUT freshness headers, retains the 30-day header cap, and accepts TTLs up to 90 days. Stale-TTL validation and documentation reflect the expanded limits. |
Apply freshness bounds to L1 writes packages/cachekit/src/cache-core.ts, packages/cachekit/src/cache/background-refresh.ts, packages/cachekit/src/l1/lru-cache.ts, packages/cachekit/src/backends/cachekitio-freshness.test.ts, packages/cachekit/src/cache/background-refresh.test.ts, packages/cachekit/src/l1/lru-cache.test.ts |
CacheImpl applies backend freshness bounds to foreground writes and write-behind. Background refreshes use the same bound. L1 expiry capping and refresh-discard operations check entry identity or version. |
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Feature
Sequence Diagram(s)
sequenceDiagram
participant CacheImpl
participant CachekitIO
participant L1Cache
CacheImpl->>CachekitIO: PUT value and TTL
CachekitIO-->>CacheImpl: Return SetResult with freshFor
CacheImpl->>CacheImpl: Convert freshFor to freshUntil
CacheImpl->>L1Cache: Cap entry expiry at freshUntil
Possibly related PRs
- cachekit-io/cachekit-ts#274: Introduced stale-grace writes and the original TTL limits that this change extends.
- cachekit-io/cachekit-ts#275: Introduced write-behind, whose L1 copy this change caps using the PUT freshness result.
- cachekit-io/cachekit-ts#304: Changes the same setEntry and in-flight write paths where this change applies freshness bounds.
Suggested reviewers: kodus-27b
Merge Risk: 
Minimal · up to 0b5a6
-
The change is mergeable after normal checks. A narrow overlapping-write case may cause an extra L2 fetch, but no material failure was established.
Pre-merge checks: 
5
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 5 functions across 13 files.
-
Finishing Touches
-
Generate docstrings
-
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@kody start-review |
1 similar comment
|
@kody start-review |
Instruction counts: PASSHead
All 36 workloads: 1 warn, 35 ok
|
|
@kody start-review |
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Providing Context (Files & MCPs)Add these hints in your PR description (or a comment) to unlock deeper checks:
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
Kody Code Review — 2 suggested fixes. 🛠️ Open Agent Prompt |
…etResult without a cast capExpiryAt looks the entry up by key and compares identity, so it needs no cast. Its doc says it deletes an entry whose cap has passed. freshUntil narrows the set result by truthiness. Test stubs check the PUT body is bytes, and the discardRefresh test freezes time.
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Providing Context (Files & MCPs)Add these hints in your PR description (or a comment) to unlock deeper checks:
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
…c85bb # Conflicts: # packages/cachekit/src/cache-core.ts
ceb431a
|
Resolved |
|
@kody review --force |
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Providing Context (Files & MCPs)Add these hints in your PR description (or a comment) to unlock deeper checks:
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
vi.mocked(fetch).getMockImplementation() is typed as fetch, so the wrapper passes url and init through unchanged with no assertion.
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Providing Context (Files & MCPs)Add these hints in your PR description (or a comment) to unlock deeper checks:
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
|
@coderabbitai review |
Action performed
|
What
Brings the CachekitIO backend in line with the protocol's per-plan maximum TTL (
spec/saas-api.md, Plan maximum TTL; Remaining Freshness, API-103 and API-104).TTL range is now 90 days.
X-CacheKit-TTL,defaultTtland thePATCH /ttlbody accept 1..7,776,000.staleTtlaccepts up to 7,775,999, andttl + staleTtlup to 7,776,000. A TTL above the tenant's plan maximum but inside the range is sent as is: the SDK does not know the plan, and the server clamps it instead of rejecting it. TheX-CacheKit-Fresh-Forgrammar cap stays 2,592,000 under its own constant (MAX_FRESH_FOR_SECONDS), so2592001and7776000still read as0.The writer's L1 copy follows the bound the store applied (API-103). A clamped write lives shorter than the TTL it stated, so the writing process must not keep serving its L1 copy for the full TTL.
Backend.setandsetWithStaleTtlmay now resolve to aSetResultcarrying thePUT2xx'sX-CacheKit-Fresh-For, parsed with the existing GET grammar. The cache caps the L1 copy atmin(ttl, fresh_for), counted from receipt of the 2xx:set(),wrap()cold missfillL1, after the L2 writewrap()withwriteBehindL1Cache.capExpiryAt()once the backgroundPUTlands (L1 is filled before it is sent)L1Write.freshUntilintocompleteRefresh, ordiscardRefreshwhen the bound is 0A malformed or
0header keeps no L1 copy, and also drops the key's older copy that the write superseded. An absent header keeps today's lifetime.X-CacheKit-Stale-TTLnever lengthens the bound. A write whose error degradation absorbed got no 2xx and keeps its copy for the TTL, as before (API-36). A non-numeric or negativefreshForfrom a custom backend fails closed, as0.PATCH /ttlnever touches an L1 bound (API-104).refreshTTLreturns a boolean, and the cache never calls it. A test pins that aPATCHresponse carryingX-CacheKit-Fresh-Forneither extends an existing copy nor creates one.Type change for custom backends
Backend.setnow returnsPromise<SetResult | void>. A backend that returnsPromise<void>still type-checks unchanged. A delegating wrapper that declares an explicitPromise<void>return type while returninginner.set(...)no longer compiles. A wrapper that doesawait inner.set(...)and returns nothing still compiles, but it silently drops the bound, so its writer's L1 copy keeps the stated TTL. Either way, a wrapper must return the inner result, as it already forwardsgetWithTtl. The three built-in wrappers (cachekitioWithLocking,cachekitioWithTTL,cachekitioFull) forward it, and the tests run every factory.Tests
staleTtl7,775,999 accepted and 7,776,000 rejected;ttl + staleTtl7,776,000 accepted and 7,776,001 rejected;PATCHbody sends 1, 2,592,001 and 7,776,000 as is.cachekitio-freshness.test.ts:Fresh-Forshorter than the TTL, for each factory; header missing; a longer header that must not extend; malformed values (0,abc,-1,2592001, 8 digits, empty); clock from receipt rather than dispatch; the stale-window write; write-behind cap and drop; SWR refresh cap and discard; the SWR threshold of a capped entry;PATCH /ttl; fail-closed custom backends.fillL1cap, the old-copy drop, the write-behindcapExpiryAt(), the refreshfreshUntil,discardRefreshand its version check, the separate Fresh-For constant, or thePUTheader parse.Docs
packages/cachekit/README.md(stale-grace limits, backends table, and a new "CachekitIO TTL and the plan maximum" note), and the TSDoc onBackend.set,setWithStaleTtl,validateTtl,CachekitIOBackendConfig.defaultTtlandWrapOptions.staleTtl. This SDK always sends a TTL, so no doc claimed that an omitted TTL never expires on CachekitIO. The note says so explicitly.BREAKING CHANGE:
Backend.setandBackend.setWithStaleTtlnow returnPromise<SetResult | void>. A custom backend wrapper mustreturn inner.set(...)(andreturn inner.setWithStaleTtl(...)): a wrapper typedPromise<void>that returns the inner call no longer compiles, and one that awaits it without returning hides thePUT's Fresh-For, so its L1 copy is not bounded by it.