refactor(domain): split Icon/WebSub/Counters out of FeedRepository

FeedRepository no longer owns favicons, WebSub subscriber lists or the
monitoring counters singleton. Each concern gets its own repository
(IconRepository, WebSubSubscriptionRepository, CountersRepository),
sharing the key schema via feed-keys. KV key strings are unchanged;
counters increment policy stays in utils/stats.ts.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Julien HerrandClaude Opus 4.7 committed 2026-05-24 00:27:33 +02:00
1 parent b347f2f625
commit a31ff42f59
13 files changed
+204 -119

No files matched your search

+5 -5
View File
@@ -4,7 +4,7 @@ import {
ICON_TTL_SECONDS,
MAX_ICON_BYTES,
} from "../config/constants";
import { FeedRepository } from "../domain/feed-repository";
import { IconRepository } from "../domain/icon-repository";
import { EmailAddress } from "../domain/value-objects/email-address";
import { logger } from "../lib/logger";
@@ -90,8 +90,8 @@ export async function cacheFaviconForDomain(
env: Env,
): Promise<void> {
try {
const repo = FeedRepository.from(env);
const existing = await repo.getIconText(domain);
const repo = IconRepository.from(env);
const existing = await repo.getText(domain);
if (existing !== null) return; // present (incl. negative) → nothing to do
const icon = await resolveIcon(domain);
@@ -102,7 +102,7 @@ export async function cacheFaviconForDomain(
}
: { data: null, contentType: "" };
await repo.putIcon(domain, JSON.stringify(record), ICON_TTL_SECONDS);
await repo.put(domain, JSON.stringify(record), ICON_TTL_SECONDS);
} catch (error) {
logger.warn("Favicon cache failed", { domain, error: String(error) });
}
@@ -115,7 +115,7 @@ export async function getCachedIcon(
domain: string,
env: Env,
): Promise<{ bytes: ArrayBuffer; contentType: string } | null> {
const record = await FeedRepository.from(env).getIconJson<IconRecord>(domain);
const record = await IconRepository.from(env).getJson<IconRecord>(domain);
if (!record || record.data === null) return null;
return {
bytes: base64ToArrayBuffer(record.data),
+1 -1
View File
@@ -87,7 +87,7 @@ describe("stats helper", () => {
],
}),
);
await kv.put("websub:a:1", "{}");
await kv.put("websub:subs:a", "{}");
await bumpCounters(kv, { emails_received: 5, feeds_created: 2 });
const stats = await getStats(env);
+6 -4
View File
@@ -1,6 +1,8 @@
import { Counters, Env, StatsResponse } from "../types";
import { logger } from "../lib/logger";
import { FeedRepository } from "../domain/feed-repository";
import { CountersRepository } from "../domain/counters-repository";
import { WebSubSubscriptionRepository } from "../domain/websub-subscription-repository";
import { getAttachmentBucket } from "./attachments";
const EMPTY_COUNTERS: Counters = {
@@ -13,7 +15,7 @@ const EMPTY_COUNTERS: Counters = {
export async function getCounters(kv: KVNamespace): Promise<Counters> {
try {
const stored = await new FeedRepository(kv).getCountersRaw();
const stored = await new CountersRepository(kv).getRaw();
return { ...EMPTY_COUNTERS, ...(stored || {}) };
} catch (error) {
logger.error("Error reading counters", { error: String(error) });
@@ -44,7 +46,7 @@ export async function bumpCounters(
current.last_feed_created_at = changes.last_feed_created_at;
if (!current.first_seen) current.first_seen = new Date().toISOString();
await new FeedRepository(kv).putCounters(current);
await new CountersRepository(kv).put(current);
} catch (error) {
logger.error("Error updating counters", { error: String(error) });
}
@@ -62,7 +64,7 @@ export async function getStats(env: Env): Promise<StatsResponse> {
const [counters, feeds, websubCount] = await Promise.all([
getCounters(env.EMAIL_STORAGE),
repo.listFeeds(),
repo.countSubscriptionKeys(),
WebSubSubscriptionRepository.from(env).countKeys(),
]);
return {
@@ -138,7 +140,7 @@ export async function setStorageSnapshot(
current.kv_bytes_estimated = snapshot.kv_bytes_estimated;
current.storage_scanned_at = new Date().toISOString();
if (!current.first_seen) current.first_seen = new Date().toISOString();
await new FeedRepository(kv).putCounters(current);
await new CountersRepository(kv).put(current);
} catch (error) {
logger.error("Error writing storage snapshot", { error: String(error) });
}
+3 -2
View File
@@ -2,12 +2,13 @@ import { Env, FeedConfig, EmailData, WebSubSubscription } from "../types";
import { generateRssFeed, generateAtomFeed } from "./feed-generator";
import { baseUrl, feedRssUrl, feedAtomUrl, feedUrl } from "./urls";
import { FeedRepository } from "../domain/feed-repository";
import { WebSubSubscriptionRepository } from "../domain/websub-subscription-repository";
export async function getSubscriptions(
feedId: string,
env: Env,
): Promise<WebSubSubscription[]> {
return FeedRepository.from(env).getSubscriptions(feedId);
return WebSubSubscriptionRepository.from(env).get(feedId);
}
export async function saveSubscriptions(
@@ -15,7 +16,7 @@ export async function saveSubscriptions(
subscriptions: WebSubSubscription[],
env: Env,
): Promise<void> {
await FeedRepository.from(env).saveSubscriptions(feedId, subscriptions);
await WebSubSubscriptionRepository.from(env).save(feedId, subscriptions);
}
export async function buildHmacSignature(