refactor: simplify Phase 1 infrastructure code

- cache-invalidation: extract isExpectedRevalidateError helper
- integration-data-cache: flatten early-return chain, extract eviction
- redis-handler: extract StoredEntry type, readStreamBody, ensureConnected
- svg-purify: single getOwnPropertyDescriptor lookup per prop
- session-scoped-providers: simplify auth flow
- retryable-loader: use function declarations
- translation/request: replace spread-in-reduce with for loop
This commit is contained in:
Thomas Camlong committed 2026-08-07 21:04:42 +02:00
1 parent b3b2d5a022
commit 33293ba1cf
8 files changed
+148 -134

No files matched your search

+10 -5
View File
@@ -13,18 +13,23 @@ import { invalidateIntegrationDataCache } from "./integration-data-cache";
// ponytail: revalidateTag throws outside Next.js request context (tests, tasks, websocket).
// Ceiling: if Next.js context detection changes, update the require/catch.
const safeRevalidateTag = (tag: string) => {
function isExpectedRevalidateError(error: unknown) {
const message = error instanceof Error ? error.message : "";
return message.includes("static generation") || message.includes("was called outside a request scope");
}
function safeRevalidateTag(tag: string) {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { revalidateTag } = require("next/cache") as typeof import("next/cache");
revalidateTag(tag, "max");
} catch (error) {
const msg = error instanceof Error ? error.message : "";
if (!msg.includes("static generation") && !msg.includes("was called outside a request scope")) {
console.warn(`[cache-invalidation] revalidateTag("${tag}") failed:`, msg);
if (!isExpectedRevalidateError(error)) {
const message = error instanceof Error ? error.message : String(error);
console.warn(`[cache-invalidation] revalidateTag("${tag}") failed:`, message);
}
}
};
}
export const invalidateBoardCache = (boardId: string, boardName?: string) => {
safeRevalidateTag(cacheTags.board(boardId));
+21 -24
View File
@@ -16,6 +16,20 @@ const MAX_ENTRIES = 200;
const cacheKey = (integrationId: string, queryKey: string) => `${integrationId}:${queryKey}`;
function evictOldestEntry() {
if (cache.size <= MAX_ENTRIES) return;
let oldestKey: string | undefined;
let oldestTime = Infinity;
for (const [key, entry] of cache) {
if (entry.timestamp < oldestTime) {
oldestTime = entry.timestamp;
oldestKey = key;
}
}
if (oldestKey) cache.delete(oldestKey);
}
export const getCachedIntegrationData = async <T>(
integrationId: string,
queryKey: string,
@@ -30,13 +44,9 @@ export const getCachedIntegrationData = async <T>(
const existing = cache.get(key) as CachedEntry<T> | undefined;
const now = Date.now();
if (existing) {
if (existing.pending) {
return existing.pending;
}
if (existing.timestamp > 0 && now - existing.timestamp < ttlMs) {
return existing.data;
}
if (existing?.pending) return existing.pending;
if (existing && existing.timestamp > 0 && now - existing.timestamp < ttlMs) {
return existing.data;
}
// Set pending synchronously BEFORE creating the fetch promise to prevent
@@ -47,19 +57,7 @@ export const getCachedIntegrationData = async <T>(
const pending = fetcher()
.then((data) => {
cache.set(key, { data, timestamp: Date.now() });
if (cache.size > MAX_ENTRIES) {
let oldest: string | undefined;
let oldestTime = Infinity;
for (const [k, v] of cache) {
if (v.timestamp < oldestTime) {
oldestTime = v.timestamp;
oldest = k;
}
}
if (oldest) cache.delete(oldest);
}
evictOldestEntry();
return data;
})
.catch((error) => {
@@ -78,9 +76,8 @@ export const getCachedIntegrationData = async <T>(
};
export const invalidateIntegrationDataCache = (integrationId: string) => {
for (const [key] of cache) {
if (key.startsWith(`${integrationId}:`)) {
cache.delete(key);
}
const prefix = `${integrationId}:`;
for (const key of cache.keys()) {
if (key.startsWith(prefix)) cache.delete(key);
}
};