feat: replace fixed 50-entry cap with size-based feed trimming

Emails are now trimmed from the oldest end when total serialised size
exceeds FEED_MAX_SIZE_BYTES (default 512 KB). Each EmailMetadata entry
stores its size so future trims are computed without re-reading KV.
Adds FEED_MAX_SIZE_BYTES, PROXY_TRUSTED_IPS and PROXY_AUTH_SECRET to Env.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-21 08:26:09 +02:00
parent 984362f637
commit 9eba4c34c6
3 changed files with 78 additions and 4 deletions
+50 -1
View File
@@ -115,7 +115,7 @@ describe("processEmail", () => {
await env.EMAIL_STORAGE.put(
`feed:${VALID_FEED_ID}:metadata`,
JSON.stringify({
emails: [{ key: "old-key", subject: "Old", receivedAt: 1 }],
emails: [{ key: "old-key", subject: "Old", receivedAt: 1, size: 100 }],
}),
);
@@ -129,4 +129,53 @@ describe("processEmail", () => {
expect(metadata.emails[0].subject).toBe("New");
expect(metadata.emails[1].subject).toBe("Old");
});
it("trims oldest emails when total size exceeds FEED_MAX_SIZE_BYTES", async () => {
await env.EMAIL_STORAGE.put(
`feed:${VALID_FEED_ID}:config`,
JSON.stringify({}),
);
const oldKey1 = `feed:${VALID_FEED_ID}:111`;
const oldKey2 = `feed:${VALID_FEED_ID}:222`;
const bigContent = "x".repeat(200);
const email1 = JSON.stringify({ subject: "Old1", from: "a@b.com", content: bigContent, receivedAt: 111, headers: {} });
const email2 = JSON.stringify({ subject: "Old2", from: "a@b.com", content: bigContent, receivedAt: 222, headers: {} });
await env.EMAIL_STORAGE.put(oldKey1, email1);
await env.EMAIL_STORAGE.put(oldKey2, email2);
await env.EMAIL_STORAGE.put(
`feed:${VALID_FEED_ID}:metadata`,
JSON.stringify({
emails: [
{ key: oldKey2, subject: "Old2", receivedAt: 222, size: email2.length },
{ key: oldKey1, subject: "Old1", receivedAt: 111, size: email1.length },
],
}),
);
const tinyEnv = { ...env, FEED_MAX_SIZE_BYTES: "50" };
const res = await processEmail(makeInput({ subject: "New" }), tinyEnv as any);
expect(res.status).toBe(200);
const metadata = await env.EMAIL_STORAGE.get(`feed:${VALID_FEED_ID}:metadata`, "json");
expect(metadata.emails).toHaveLength(1);
expect(metadata.emails[0].subject).toBe("New");
const deleted1 = await env.EMAIL_STORAGE.get(oldKey1, "json");
const deleted2 = await env.EMAIL_STORAGE.get(oldKey2, "json");
expect(deleted1).toBeNull();
expect(deleted2).toBeNull();
});
it("keeps entries within size budget untouched", async () => {
await env.EMAIL_STORAGE.put(
`feed:${VALID_FEED_ID}:config`,
JSON.stringify({}),
);
const bigEnv = { ...env, FEED_MAX_SIZE_BYTES: String(10 * 1024 * 1024) };
await processEmail(makeInput({ subject: "First" }), bigEnv as any);
await processEmail(makeInput({ subject: "Second" }), bigEnv as any);
const metadata = await env.EMAIL_STORAGE.get(`feed:${VALID_FEED_ID}:metadata`, "json");
expect(metadata.emails).toHaveLength(2);
});
});