mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-21 06:13:48 +00:00
06c436c36a
Move four DDD tensions on the Feed aggregate to ground: - #1 The aggregate now holds a domain FeedState (camelCase) instead of the snake_case FeedConfig DTO; infrastructure/feed-mapper.ts owns the FeedState<->FeedConfig/FeedListItem translation as the sole snake_case site outside the HTTP edge. - #3 Replace the edit() recomputeExpiry control flag with a Lifetime VO: passing a lifetime recomputes expiry, omitting it preserves the current one (the dashboard quick-edit path). - #4 Domain events carry their own feedId; dispatchFeedEvents centralizes the drain+dispatch in the application layer (no more manual pullEvents at call sites), keeping infra->application dependency direction intact. - #6 Rename FeedId.fromTrusted to FeedId.unchecked to make the absence of revalidation explicit. Adds Lifetime + feed-mapper round-trip tests. 353 tests green, tsc clean, wrangler dry-run OK. Docs (CLAUDE.md) synced. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
41 lines
1.4 KiB
TypeScript
41 lines
1.4 KiB
TypeScript
import { nouns } from "../../data/nouns";
|
|
|
|
// Feed IDs are noun1.noun2.XY (two lowercase nouns + a 2-digit suffix).
|
|
const FEED_ID_IN_ADDRESS = /^([a-z]+\.[a-z]+\.\d{2})@/i;
|
|
|
|
/**
|
|
* A feed identifier. `parse` pulls it from the local part of an inbound email
|
|
* address; `generate` mints a fresh one. The original casing is preserved.
|
|
*/
|
|
export class FeedId {
|
|
private constructor(readonly value: string) {}
|
|
|
|
/** Extract the feed id from an inbound address (`noun.noun.NN@domain`). */
|
|
static parse(emailAddress: string): FeedId | null {
|
|
const match = emailAddress.match(FEED_ID_IN_ADDRESS);
|
|
return match ? new FeedId(match[1]) : null;
|
|
}
|
|
|
|
/**
|
|
* Wrap a string as a FeedId WITHOUT revalidating it. The caller asserts the id
|
|
* originated from our own minting — a route param echoing a stored id, a
|
|
* `feeds:list` entry, or an email/KV key. The name is deliberately blunt: a
|
|
* wrong id is not rejected here, it simply misses in KV and 404s downstream.
|
|
* Untrusted external input (an inbound address) must go through `parse` instead.
|
|
*/
|
|
static unchecked(value: string): FeedId {
|
|
return new FeedId(value);
|
|
}
|
|
|
|
static generate(): FeedId {
|
|
const noun1 = nouns[Math.floor(Math.random() * nouns.length)];
|
|
const noun2 = nouns[Math.floor(Math.random() * nouns.length)];
|
|
const number = Math.floor(Math.random() * 90) + 10;
|
|
return new FeedId(`${noun1}.${noun2}.${number}`);
|
|
}
|
|
|
|
toString(): string {
|
|
return this.value;
|
|
}
|
|
}
|