Her projenin o klasik elemanı vardır: fetch çağrısı, arkasından as ileecast edilen bir tip.
Derleyici memnundur, tip dosyada yazılıdır — ama sunucu bir gün data yerine dada gönderdiğinde
uygulama çalışma anında patlar. Sorunun adı şu: tip, gerçeklikle runtime'da değil compile-time'da
karşılaşıyor. Çözüm de tek cümlelik: şemayı bir kez yaz, tipleri ondan üret, sınırda doğrula.
Şema tek gerçek kaynağı olsun
Zod gibi bir kütüphanede şema hem doğrulayıcı hem tip üreticidir. Şemayı bir kez tanımlarsınız; TypeScript tipi ondan türetilir. Böylece "dokümandaki tip" ile "kabul edilen tip" arasında kopukluk doğmaz:
// lib/schemas.ts
import { z } from 'zod';
// API'nin gerçekten döndüğü şekil — TEK GERÇEK KAYNAK
export const Makale = z.object({
id: z.string().uuid(),
baslik: z.string().min(1),
ozet: z.string().max(300),
yayinda: z.boolean(),
etiketler: z.array(z.string()).default([]),
yayinTarihi: z.string().datetime(), // ISO 8601
});
// Tipi elle yazmak YOK; şemadan üretiliyor:
export type Makale = z.infer<typeof Makale>;
z.infer sayesinde şemada yaptığınız her değişiklik, tiplere anında yansır. Bir alanı zorunlu
yaptığınızda onu okuyan bütün kod derleme anında uyarılır — production'da değil, editörde.
Sınırda doğrula, sonra tipli veriyle çalış
Doğrulama, verinin dışarıdan geldiği her yerde yapılmalı: API yanıtı, form verisi, localStorage,
URLSearchParams. Aşağıdaki apiGet, başarılı her yanıtı Makale şemasından geçirir; şema
bozulursa hata fırlatır ve fonksiyon asla yanlış tipli veriyi döndürmez:
// lib/api.ts
import { Makale, type Makale } from './schemas';
export class ApiHata extends Error {
constructor(public detaylar: unknown) {
super('API yanıtı şemaya uymadı');
}
}
export async function makaleGetir(id: string): Promise<Makale> {
const res = await fetch(`/api/articles/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json: unknown = await res.json(); // bilinçli olarak unknown
// Sınırda doğrulama: tip güvenliği BURADA kuruluyor.
const sonuc = Makale.safeParse(json);
if (!sonuc.success) throw new ApiHata(sonuc.error);
return sonuc.data; // artık tip: Makale
}
Birkaç inceliğe dikkat: res.json() sonucunu unknown'a alıyoruz — any değil. unknown'un
kullanılmadan önce daraltılması gerekir; bu yüzden şema doğrulaması olmadan derleyici zaten
geçit vermez. İkincisi, safeParse başarısızsa hatayı yutmuyoruz: ApiHata ile loglanabilir
bir iz bırakıyoruz. Üçüncüsü, dönüş tipi Promise<Makale> — çağıran taraf için artık
"umluyorum" değil "bunu alıyorum" sözü var.
Hata akışını da tiplendirebilirsiniz. Birleşik tip (union) ile "başarı" ve "başarısızlık"
hallerini fonksiyon imzasına yazarsınız; çağıran taraf if ile ayrımadan derleyici geçit
vermez:
// Sonucu birleşik tip ile modellemek: hata yönetimi tip sisteminin parçası
type Sonuc<T> = { ok: true; veri: T } | { ok: false; hata: string };
export async function guvenliMakale(id: string): Promise<Sonuc<Makale>> {
try {
return { ok: true, veri: await makaleGetir(id) };
} catch (e) {
return { ok: false, hata: e instanceof Error ? e.message : 'bilinmeyen' };
}
}
// Çağıran taraf:
const s = await guvenliMakale(id);
if (s.ok) console.log(s.veri.baslik); // s.veri burada kesin Makale
else console.error(s.hata); // derleyici ok'u zorunlu tutuyor
Bedeli ve getirisi
Ek maliyet gerçek: şema yazmak, hata yönetimini düşünmek, unknown'la uğraşmak. Buna karşılık
getirisi somut: field adı değişince derleyici patlıyor, "undefined hatası production'da"
teması azalıyor, dokümantasyon ihtiyacı şemaya devrediliyor. Runtime verisiyle compile-time
tipleri aynı kaynaktan akıttığınızda tip güvenliği bir lütuf değil, mimarinin doğal sonucu
haline geliyor.
Okur Yorumları (0)