refactor(domain): make FeedId circulate through the domain and repository

FeedId is now the type of Feed.id and of every single-feed method on
FeedRepository; callers wrap raw strings via FeedId.fromTrusted at the
repository boundary. String-medium operations (URLs, logs, JSON,
list registry, email keys) stay string. Drop the redundant
generateFeedId wrapper in favour of FeedId.generate.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Julien HerrandClaude Opus 4.7 committed 2026-05-24 00:44:24 +02:00
1 parent 05388b45c8
commit ab1c15e69a
18 files changed
+162 -124

No files matched your search

+4 -2
View File
@@ -1,6 +1,7 @@
import { Env, FeedConfig, EmailData } from "../types";
import { MAX_FEED_ITEMS } from "../config/constants";
import { FeedRepository } from "../domain/feed-repository";
import { FeedId } from "../domain/value-objects/feed-id";
export interface FeedData {
feedConfig: FeedConfig;
@@ -12,11 +13,12 @@ export async function fetchFeedData(
env: Env,
): Promise<FeedData | null> {
const repo = FeedRepository.from(env);
const id = FeedId.fromTrusted(feedId);
const feedMetadata = await repo.getMetadata(feedId);
const feedMetadata = await repo.getMetadata(id);
if (!feedMetadata) return null;
const feedConfig = (await repo.getConfig(feedId)) ?? {
const feedConfig = (await repo.getConfig(id)) ?? {
title: `Newsletter Feed ${feedId}`,
description: "Converted email newsletter",
language: "en",
-9
View File
@@ -1,9 +0,0 @@
import { FeedId } from "../domain/value-objects/feed-id";
/**
* Generates a random feed ID in the format noun1.noun2.XY
* @returns A random feed ID string
*/
export function generateFeedId(): string {
return FeedId.generate().value;
}
+2 -1
View File
@@ -3,6 +3,7 @@ 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 { FeedId } from "../domain/value-objects/feed-id";
import { getAttachmentBucket } from "./attachments";
const EMPTY_COUNTERS: Counters = {
@@ -109,7 +110,7 @@ export async function scanKvUsage(kv: KVNamespace): Promise<{ bytes: number }> {
const repo = new FeedRepository(kv);
const feeds = await repo.listFeeds();
for (const feed of feeds) {
const metadata = await repo.getMetadata(feed.id);
const metadata = await repo.getMetadata(FeedId.fromTrusted(feed.id));
if (!metadata) continue;
for (const email of metadata.emails) {
bytes += email.size ?? 0;
+4 -2
View File
@@ -3,6 +3,7 @@ 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";
import { FeedId } from "../domain/value-objects/feed-id";
export async function getSubscriptions(
feedId: string,
@@ -47,9 +48,10 @@ async function buildFeedXml(
format: "rss" | "atom" = "rss",
): Promise<string | null> {
const repo = FeedRepository.from(env);
const id = FeedId.fromTrusted(feedId);
const [feedMetadata, rawConfig] = await Promise.all([
repo.getMetadata(feedId),
repo.getConfig(feedId),
repo.getMetadata(id),
repo.getConfig(id),
]);
if (!feedMetadata) return null;