- lib/istek-cache: modül seviyesi in-flight dedup + 5dk TTL cache (SWR bağımlılığı olmadan); caller iptali paylaşılan fetch'i düşürmez - /api/facetler'in 5 çağrı yeri (hero, cta, kapı, sihirbaz adımları, demo) ve /api/katalog-ara cache'e bağlandı — sihirbazda 2↔3 adım geçişi ve tekrarlanan aramalar artık ağa çıkmıyor; /api/soru GET dedup-only (ttl 0) - katalog-arama: elle yukleniyor state'i kalktı, "cevaplanmamış sorgu" türetmesi + startTransition; rehber puanlaması Schwartzian; ⌘K listener'ı useEffectEvent ile tek sefer bağlanıyor - sihirbaz-adimlar: il sort/filter useMemo (çip başına 81 eleman kopyalanıp sıralanıyordu, harita dizisi referansı sabitlendi) - sohbet-client: stream chunk güncellemeleri startTransition'da — cevap akarken input yazımı bloklanmıyor - sihirbaz-profil: modül cache + event/storage geçersizlemesi (tek "+" tıklamasında 3 localStorage okuma + JSON.parse vardı) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 lines
2.3 KiB
TypeScript
63 lines
2.3 KiB
TypeScript
// İstemci tarafı GET istekleri için modül seviyesi in-flight dedup + TTL
|
||
// cache. SWR bağımlılığı yerine minimal el yapımı katman: /api/facetler beş
|
||
// farklı bileşenden aynı parametrelerle çağrılıyor, katalog araması aynı
|
||
// sorguyu tekrar tekrar atıyordu.
|
||
//
|
||
// - Aynı URL uçuştayken ikinci çağrı aynı promise'i bekler (dedup).
|
||
// - Başarılı yanıt ttlMs boyunca bellekte kalır; ttlMs: 0 = yalnız dedup
|
||
// (kullanıcıya özel, mutasyon sonrası tazelenmesi gereken uçlar için).
|
||
// - Paylaşılan fetch'e caller signal'ı GEÇİLMEZ: bir abonenin iptali
|
||
// diğerlerinin isteğini düşürmesin. İptal yalnızca o abonenin bekleyişini
|
||
// AbortError ile sonlandırır (katalog aramasının debounce/abort akışı
|
||
// davranış değiştirmeden buna bağlanır).
|
||
|
||
type Kayit = { zaman: number; veri: unknown };
|
||
|
||
const bellek = new Map<string, Kayit>();
|
||
const ucusta = new Map<string, Promise<unknown>>();
|
||
const MAX_KAYIT = 50;
|
||
const VARSAYILAN_TTL_MS = 5 * 60_000;
|
||
|
||
export async function onbellekliJsonGetir<T>(
|
||
url: string,
|
||
{
|
||
ttlMs = VARSAYILAN_TTL_MS,
|
||
signal,
|
||
}: { ttlMs?: number; signal?: AbortSignal } = {},
|
||
): Promise<T> {
|
||
const kayit = bellek.get(url);
|
||
if (kayit && ttlMs > 0 && Date.now() - kayit.zaman < ttlMs) {
|
||
return kayit.veri as T;
|
||
}
|
||
|
||
let soz = ucusta.get(url);
|
||
if (!soz) {
|
||
soz = fetch(url)
|
||
.then(async (res) => {
|
||
if (!res.ok) throw new Error(`istek-cache: ${res.status} (${url})`);
|
||
const veri: unknown = await res.json();
|
||
if (ttlMs > 0) {
|
||
bellek.set(url, { zaman: Date.now(), veri });
|
||
// Basit LRU kırpması: en eski kayıt düşer (insertion order)
|
||
if (bellek.size > MAX_KAYIT) {
|
||
const enEski = bellek.keys().next().value;
|
||
if (enEski !== undefined) bellek.delete(enEski);
|
||
}
|
||
}
|
||
return veri;
|
||
})
|
||
.finally(() => ucusta.delete(url));
|
||
ucusta.set(url, soz);
|
||
}
|
||
|
||
if (!signal) return soz as Promise<T>;
|
||
return new Promise<T>((cozul, reddet) => {
|
||
const iptalEt = () => reddet(new DOMException("Aborted", "AbortError"));
|
||
if (signal.aborted) return iptalEt();
|
||
signal.addEventListener("abort", iptalEt, { once: true });
|
||
(soz as Promise<T>).then(cozul, reddet).finally(() => {
|
||
signal.removeEventListener("abort", iptalEt);
|
||
});
|
||
});
|
||
}
|