fix(feed): stop persisting legacy podcast.episodes

search-time parseRSSFeed once embedded the full episode history
inside Feed.podcast (2,100+ stale copies, 3.8 MB of config). Nothing
reads it — feed.episodes is the source of truth — so load and save
now strip podcast.episodes, and searchByFeedUrl drops them at the
parse site.
This commit is contained in:
2026-09-06 18:46:42 -04:00
parent 132d2079f7
commit 8a173a5180
3 changed files with 96 additions and 8 deletions

View File

@@ -83,6 +83,18 @@ function reviveDates(feed: Feed): Feed {
})),
};
}
/** Config-legacy baggage: search-time parseRSSFeed once embedded the full
* episode history inside podcast.episodes (2,100+ stale copies, 3.8 MB of
* config). Nothing reads them — feed.episodes is the source of truth — so
* every load/save drops them. */
function stripLegacyPodcastEpisodes(feed: Feed): Feed {
if (!("episodes" in feed.podcast)) return feed;
const { episodes: _legacy, ...podcast } = feed.podcast;
void _legacy;
return { ...feed, podcast: podcast as Feed["podcast"] };
}
/** Load feeds from config.json, pruning episodes outside the retention
* window (completed downloads always kept). When anything was pruned, the
* pruned list is rewritten to config.json (startup cleanup for legacy
@@ -93,7 +105,9 @@ export async function loadFeedsFromFile(
try {
const cfg = await loadConfig();
if (!Array.isArray(cfg.feeds)) return [];
const feeds = cfg.feeds.map(reviveDates);
const feeds = cfg.feeds
.map(reviveDates)
.map(stripLegacyPodcastEpisodes);
const downloadedIds = await readDownloadedEpisodeIds();
const now = new Date();
let prunedAny = false;
@@ -122,12 +136,14 @@ export function saveFeedsToFile(feeds: Feed[], windowDays?: number): void {
(async () => {
try {
const downloadedIds = await readDownloadedEpisodeIds();
const pruned = feeds.map((f) => ({
...f,
episodes: f.episodes.filter((ep) =>
episodeIsPersistable(ep, downloadedIds, new Date(), windowDays),
),
}));
const pruned = feeds
.map(stripLegacyPodcastEpisodes)
.map((f) => ({
...f,
episodes: f.episodes.filter((ep) =>
episodeIsPersistable(ep, downloadedIds, new Date(), windowDays),
),
}));
updateConfig({ feeds: pruned });
} catch {
updateConfig({ feeds }); /* never lose data on an error path */

View File

@@ -84,7 +84,11 @@ export const searchByFeedUrl = async (
try {
const xml = await fetchFeedXml(trimmed);
if (xml === null) return [];
const podcast = parseRSSFeed(xml, trimmed);
// Full parse's episodes are dead weight here (2,100+ stale copies were
// previously persisted inside Feed.podcast): addFeed refetches through
// fetchEpisodes and nothing reads Podcast.episodes off a search result.
const { episodes: _episodes, ...podcast } = parseRSSFeed(xml, trimmed);
void _episodes;
return [
{