feat(#55): configurable cache retention via PULLMD_CACHE_RETENTION_DAYS - #56
Merged
Merged
Conversation
The 90-day window was hardcoded in lib/cache.js (share lookup, prune, expiring-soon stat) plus one fallback literal in server.js. It is now read from PULLMD_CACHE_RETENTION_DAYS: a non-negative integer, default 90, where 0 means unlimited (no prune, no age condition on /s/:id, expiringSoon 0). Unset or empty falls back silently, anything malformed warns once and falls back. The PWA footer renders unbegrenzt/unlimited for 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFVGVrGAFP1TuYVGGmEoHK
…ompose Documents the configurable cache retention shipped in c6c0e76: the env var block in .env.example, a configuration-table row plus the Cache & TTLs and architecture lines in README.md, the bilingual TTL table and share-link bullets in public/help.html, and an Unreleased/Added entry in CHANGELOG.md. Both compose files enumerate environment variables explicitly, so they now pass PULLMD_CACHE_RETENTION_DAYS through; without that line the variable never reaches the container. Every remaining 90 in these files now reads as a default rather than a fixed value, and the session-cookie 90-day lines are untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFVGVrGAFP1TuYVGGmEoHK
readCacheRetentionDays accepted any digit string, so an absurd value went
through unchecked. A value like 9999999 produced datetime('now', '-9999999
days'), which is outside SQLite's date range and evaluates to NULL: every
/s/:id lookup silently 404'd and expiringSoon read 0. A 22-digit value became
1e+22, an integer as far as Number.isInteger is concerned, whose interpolated
modifier is not valid SQLite syntax at all, with the same silent outcome. A
309-digit value parsed to Infinity, failed the Number.isInteger check inside
createCache and crashed the server at startup, which the "never crash on a bad
env value" rule forbids.
Both the parser and createCache now reject anything above the new
MAX_CACHE_RETENTION_DAYS (36500, 100 years): the parser warns once and falls
back to 90, the constructor throws. .env.example, the README row and the
CHANGELOG entry name the accepted range and spell out that unset or empty
still means 90 without a warning.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFVGVrGAFP1TuYVGGmEoHK
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.
Closes #55.
The 90-day cache retention was hardcoded in four places in
lib/cache.js(share lookup, prune, expiring-soon window, reportedretentionDays) plus one fallback literal inserver.js. It is now read from a single env var.PULLMD_CACHE_RETENTION_DAYS0to36500. Default90, so existing instances behave exactly as before.0= unlimited: no prune statement is prepared,/s/:idlookups carry no age condition,expiringSoonis 0 without a query. The cache doubles as an archive.90(the compose files pass${VAR:-}, so empty is the normal unconfigured case). Anything else warns once at startup and falls back to90; the server never refuses to start over this value.expiringSoonfollows the setting: rows within 10 days of being pruned (max(retention - 10, 0)), matching the old 80-of-90 window at the default.GET /api/storagereports the effective value; the PWA footer renders "unbegrenzt" / "unlimited" for0.Wiring
createCache(dbPath, { retentionDays })validates the option before opening the database, since the prepared statements bake the window into their SQL. The env parserreadCacheRetentionDays(env, warn)is the only coercion boundary and is unit-tested in isolation.server.jsreads the variable once at startup and passes it to both the cache andcreateApp, so a malformed value warns exactly once and both consumers agree.Docs
.env.example, README (config table, Cache & TTLs, architecture list),help.htmlDE+EN (three pairs), CHANGELOG. Session-cookie TTL mentions of "90 days" are a different feature and untouched.Caveat documented in
.env.exampleand README: lowering the value on a running instance prunes every row older than the new value on the next cache write.Tests
1264 baseline plus new tests for the parser (unset/empty silent, trimming,
0, malformed and out-of-range values), the cache option (share lookup, prune + orphan sweep, expiring-soon clamp, unlimited, constructor rejection) and/api/storagewith and without a cache.🤖 Generated with Claude Code
https://claude.ai/code/session_01TFVGVrGAFP1TuYVGGmEoHK