Skip to content
~/sph.sh

TypeScript'in Temel Ama Az Bilinen Özellikleri: Production-Ready Type Safety

Production kod kalitesini önemli ölçüde iyileştiren 7 az bilinen TypeScript özelliğini keşfedin: satisfies operator, noUncheckedIndexedAccess, branded types, discriminated unions, type predicates, template literals ve infer keyword.

Abstract

TypeScript 2025'te GitHub'da en çok kullanılan dil haline geldi, Ağustos ayında Python'u geçerek yıl bazında %66 contributor artışıyla. Ancak birçok geliştirici sadece type system'in yüzeyini kazıyor. Bu yazı, production kod kalitesini önemli ölçüde iyileştiren 7 az bilinen özelliği inceliyor: configuration validation için satisfies operator, array güvenliği için noUncheckedIndexedAccess, nominal typing için branded types, exhaustiveness checking ile discriminated unions, type predicates vs assertion functions, string patterns için template literal types ve type extraction için infer keyword. Her özellik, spesifik production problemlerini sıfır runtime overhead ile çözüyor.

Problem Context

TypeScript codebase'leriyle çalışırken, önlenebilir runtime hatalara yol açan tekrarlayan patternler fark ettim. strict mode'u etkinleştirmemize rağmen, ekipler hala undefined array access, farklı entity type'ları arasında ID karışıklığı, handle edilmeyen state machine case'leri ve sistem sınırlarında zayıf validation gibi sorunlarla karşılaşıyor.

Temel problem TypeScript'in yetenekleri değil - güçlü type system özelliklerinin yeterince kullanılmaması. Birçok geliştirici basit type annotation'larda kalıyor, production'a ulaşmadan önce tüm bir bug sınıfını yakalayabilecek compile-time garantileri kaçırıyor.

Bu özelliklerin çözdüğü spesifik teknik problemler:

Type Safety Gaps: Her yerde any kullanmak TypeScript'in amacını boşa çıkarır ve type hatalarının runtime'a sızmasına izin verir.

Array Access Without Guards: array[5] expression'ı undefined dönebilir, ancak TypeScript'in default konfigürasyonu bunu uyarmaz - strict mode bile etkinken.

Structural Type Confusion: TypeScript structural typing kullanır, yani UserID ve OrderID (her ikisi de number) birbirinin yerine kullanılabilir, ID'ler karıştığında data corruption'a yol açar.

Incomplete Union Handling: Bir state type'ına yeni case eklemek mevcut switch statement'ları bozmaz, production'da handle edilmeyen case'lere neden olur.

Weak Validation Boundaries: API'lerden gelen external data runtime validation gerektirir, ancak validation mantığı ile type narrowing arasındaki bağlantı genellikle belirsizdir.

Configuration Type Loss: Configuration object'leri üzerinde type assertion kullanmak, hataları yakalayabilecek değerli type bilgisini kaybettirir.

Nested Type Extraction: Kompleks generic type'lar manuel type extraction gerektirir, duplikasyon ve drift'e yol açar.

Technical Requirements

Bu problemleri etkili şekilde çözmek için ihtiyacımız olan çözümler:

  1. Compile-time garantiler sağlamalı runtime overhead olmadan
  2. Gradual olarak entegre olmalı mevcut codebase'lere full rewrite gerektirmeden
  3. Net migration path'leri sunmalı gerçekçi zaman tahminleriyle
  4. Modern TypeScript ile çalışmalı (5.0+) ve popüler framework'lerle
  5. Büyük codebase'lere scale etmeli önemli compilation time impact'i olmadan

Hedef, deployment'tan sonra değil development sırasında bug'ları yakalayan production-ready type safety.

Implementation

1. satisfies Operator ile Const Assertions

satisfies operator (TypeScript 4.9+), type validation'ı precise literal type inference ile birleştirir - hem compile-time checking hem de spesifik type'lar sağlar.

Problem: Configuration object'leri bir type schema'ya karşı validation gerektirir, ancak as type assertion kullanmak literal type bilgisini kaybettirir.

typescript
// satisfies olmadan - type bilgisi kaybolurconst routes = {  home: { path: '/', methods: ['GET', 'POST'] },  api: { path: '/api', methods: ['POST'] }} as const;// routes.home.path '/' (iyi), ama validation yok

Çözüm: Immutability artı validation için as const satisfies kullan.

typescript
type Route = {  path: string;  methods: readonly ('GET' | 'POST' | 'PUT' | 'DELETE')[];};
type Routes = Record<string, Route>;
const routes = {  home: { path: '/', methods: ['GET', 'POST'] },  api: { path: '/api', methods: ['POST'] },  // Comment'i kaldırırsan TypeScript error:  // invalid: { path: '/bad', methods: ['INVALID'] }} as const satisfies Routes;
// Şimdi: type-checked VE precise literal typesroutes.home.path; // type: '/' (literal, string değil)routes.api.methods; // type: readonly ['POST']

Ne zaman kullan: API konfigürasyonları, theme tanımları, routing table'ları, feature flag'leri.

Ne zaman KULLANMA: Dynamic runtime data, sık değişen value'lar.

Real-world impact: Bu pattern, routing sisteminde geçersiz HTTP method'larının register edildiği configuration hatalarını yakaladı. TypeScript error'u runtime failure'a yol açmak yerine development sırasında hemen göründü.

2. noUncheckedIndexedAccess - Eksik Strict Flag

Kritik bir konfigürasyon detayı: noUncheckedIndexedAccess compiler option'ı strict mode'a dahil değil, ancak "Cannot read property of undefined" hatalarının tüm bir sınıfını önlüyor.

Problem: Array ve object indexed access undefined dönebilir, ancak TypeScript'in default davranışı bu gerçeği yansıtmıyor.

typescript
// tsconfig.json - default strict mode{  "compilerOptions": {    "strict": true  }}
// Bu kod güvenli görünüyor ama değilconst users = ['Alice', 'Bob'];const user = users[5]; // type: string (YANLIŞ - aslında undefined!)user.toUpperCase(); // Runtime error: Cannot read property 'toUpperCase' of undefined

Çözüm: noUncheckedIndexedAccess'i explicit olarak enable et.

typescript
// tsconfig.json - production-ready strict mode{  "compilerOptions": {    "strict": true,    "noUncheckedIndexedAccess": true  // Bunu ekle!  }}
// Şimdi TypeScript gerçeği yansıtıyorconst users = ['Alice', 'Bob'];const user = users[5]; // type: string | undefined (DOĞRU)
// TypeScript undefined'ı handle etmeni zorluyorif (user) {  user.toUpperCase(); // ✓ Güvenli}
// Ya da optional chaining kullanconst upperName = users[5]?.toUpperCase();

Migration impact: Bu option'ı orta büyüklükte bir codebase'de (30k LOC) etkinleştirmek yaklaşık 150 compilation error üretti, çoğu optional chaining ile fix edildi. Zaman yatırımı yaklaşık 3 gündü, ancak getirisi birden fazla production incident'a neden olmuş bir error kategorisini ortadan kaldırmaktı.

Neden az kullanılıyor: Bu option strict mode'un parçası değil, bu yüzden birçok geliştirici var olduğunu bilmiyor.

3. Nominal Type Safety için Branded Types

TypeScript structural typing kullanır, yani aynı structure'a sahip iki type birbirinin yerine kullanılabilir. Bu güçlü olsa da, nominal typing davranışı istediğinde subtle bug'lara yol açabilir.

Problem: Structurally identical type'lar (ikisi de number) karışabilir.

typescript
// Problemtype UserID = number;type OrderID = number;
function getUser(id: UserID) { /* ... */ }function getOrder(id: OrderID) { /* ... */ }
const userId: UserID = 123;const orderId: OrderID = 456;
getUser(orderId); // ✓ TypeScript buna izin veriyor (KÖTÜ!)

Bu production sistemlerde gerçek bir problem. Bir multi-tenant uygulamada, tenant ID'lerini user ID'leriyle karıştırmak data leakage'a neden oldu - compile time'da önlenebilecek bir güvenlik olayı.

Çözüm: Branded type'lar type level'da nominal benzeri davranış yaratır.

typescript
// Generic brand utilitytype Brand<K, T> = K & { readonly __brand: T };
type UserID = Brand<number, 'UserID'>;type OrderID = Brand<number, 'OrderID'>;
// Branded value'lar yaratmak için constructor function'larconst UserID = (id: number): UserID => id as UserID;const OrderID = (id: number): OrderID => id as OrderID;
const userId = UserID(123);const orderId = OrderID(456);
getUser(orderId); // ✗ TypeScript error: Type 'OrderID' is not assignable to type 'UserID'

Production use case'leri:

  • Database ID'leri (multi-tenant sistemlerde ID karışıklığını önleme)
  • Para birimi değerleri (USD vs EUR)
  • Email adresleri vs genel string'ler
  • Validate edilmiş vs validate edilmemiş user input

Performance: Sıfır runtime overhead - brand sadece type level'da var olur ve compilation sırasında silinir.

Advanced pattern: Branded type'ları validation function'larıyla birleştir.

typescript
type Email = Brand<string, 'Email'>;
const Email = (value: string): Email => {  if (!value.includes('@') || !value.includes('.')) {    throw new Error('Invalid email format');  }  return value as Email;};
// Artık email variable'ları validate edildiği garantifunction sendEmail(to: Email) {  // Tekrar validate etmeye gerek yok - type system garanti ediyor}

4. Exhaustiveness Checking ile Discriminated Unions

State machine'ler ve API response'ları, never type üzerinden exhaustiveness checking ile birleştirilmiş discriminated union'lardan büyük fayda sağlar.

Problem: Tüm case'leri handle etmeyen switch statement'lar runtime failure'lara yol açar.

typescript
type Result<T> =  | { status: 'success'; data: T }  | { status: 'error'; error: Error }  | { status: 'loading' };
// Exhaustiveness checking olmadanfunction handleResult<T>(result: Result<T>) {  switch (result.status) {    case 'success':      return result.data;    case 'error':      throw result.error;    // 'loading' case'ini unuttuk - error yok!  }  // Loading state için undefined döner - bug!}

Çözüm: Exhaustiveness'i zorlamak için never type'ı kullan.

typescript
function handleResult<T>(result: Result<T>) {  switch (result.status) {    case 'success':      return result.data;    case 'error':      throw result.error;    case 'loading':      return null;    default:      // Bu TypeScript'i tüm case'leri kontrol etmeye zorlar      const exhaustive: never = result;      throw new Error(`Unhandled case: ${exhaustive}`);  }}
// Yeni bir status eklemek compilation'ı bozartype Result<T> =  | { status: 'success'; data: T }  | { status: 'error'; error: Error }  | { status: 'loading' }  | { status: 'cancelled' }; // TypeScript şimdi handleResult'ta error verir

Neden güçlü: Yeni bir union member eklediğinde, TypeScript güncellenmesi gereken her konumu hemen highlight eder. Bu, potansiyel bir runtime bug'ı compile-time task listesine dönüştürür.

Real-world uygulama: Bir API client library'de, bu pattern yeni bir response state ('retry') eklediğinde 47 konumda compilation'ı bozduğundan emin oldu. Bu pattern olmadan, bunlar subtle runtime bug'lar olurdu.

5. Type Predicates vs Assertion Functions

TypeScript, type narrowing için iki pattern sunar: type predicates ve assertion functions. Her birini ne zaman kullanacağını anlamak, temiz ve type-safe validation mantığı için kritik.

Type Predicate: Boolean döner, conditional check'lerde kullanılır.

typescript
function isString(value: unknown): value is string {  return typeof value === 'string';}
// Conditional'larda kullanconst data: unknown = getSomeData();if (isString(data)) {  data.toUpperCase(); // ✓ data burada string}

Assertion Function: Throw eder veya void döner, scope'un geri kalanı için type'ı daraltır.

typescript
function assertString(value: unknown): asserts value is string {  if (typeof value !== 'string') {    throw new Error('Not a string');  }}
// Validation için kullanconst data: unknown = getSomeData();assertString(data); // string değilse throw ederdata.toUpperCase(); // ✓ data scope'un geri kalanında string

Advanced örnek: Custom domain object validation.

typescript
type User = {  id: number;  email: string;  name: string;};
function assertUser(obj: unknown): asserts obj is User {  if (    typeof obj !== 'object' ||    obj === null ||    !('id' in obj) ||    !('email' in obj) ||    !('name' in obj) ||    typeof obj.id !== 'number' ||    typeof obj.email !== 'string' ||    typeof obj.name !== 'string'  ) {    throw new Error('Invalid user object');  }}
// API response validationasync function fetchUser(id: number): Promise<User> {  const response = await fetch(`/api/users/${id}`);  const data: unknown = await response.json();
  assertUser(data); // structure'ı validate eder  return data; // ✓ TypeScript bunun User olduğunu biliyor}

Ne zaman predicate kullan: Optional check'ler, filter işlemleri, conditional mantık.

Ne zaman assertion kullan: Mandatory validation, parse function'ları, sistem sınırlarındaki guard clause'lar.

Kritik gotcha: Assertion function'lar failure'da throw etmeli, false dönmemeli. False dönmek type'ı daraltmaz.

6. String Pattern'ler için Template Literal Types

Template literal types (TypeScript 4.1+), sıfır runtime cost ile type-safe string manipulation sağlar.

Problem: String pattern'ler ve convention'lar compile-time validation gerektirir.

CSS Unit Types:

typescript
type CSSUnit = 'px' | 'em' | 'rem' | '%';type CSSValue<T extends string> = `${number}${T}`;
type Padding = CSSValue<CSSUnit>;const padding: Padding = '10px'; // ✓const invalid: Padding = '10abc'; // ✗ Error

Event Handler Naming:

typescript
type EventName = 'click' | 'focus' | 'blur' | 'hover';type EventHandler<T extends EventName> = `on${Capitalize<T>}`;
type ClickHandler = EventHandler<'click'>; // 'onClick'type FocusHandler = EventHandler<'focus'>; // 'onFocus'
type Handlers = {  [K in EventName as EventHandler<K>]: (event: Event) => void;};// Generates: { onClick: ..., onFocus: ..., onBlur: ..., onHover: ... }

API Route Typing:

typescript
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';type Endpoint = '/users' | '/posts' | '/comments';type Route = `${HTTPMethod} ${Endpoint}`;
const route: Route = 'GET /users'; // ✓const invalid: Route = 'GET /invalid'; // ✗ Error
// Type-safe route matcherfunction matchRoute(route: Route): void {  // TypeScript route'un valid olduğunu biliyor}

Path Parameter Extraction (advanced):

typescript
type ExtractParams<T extends string> =  T extends `${infer _Start}:${infer Param}/${infer Rest}`    ? { [K in Param | keyof ExtractParams<`/${Rest}`>]: string }    : T extends `${infer _Start}:${infer Param}`    ? { [K in Param]: string }    : {};
type UserRoute = '/users/:userId/posts/:postId';type Params = ExtractParams<UserRoute>; // { userId: string; postId: string }
function getPost(params: Params) {  console.log(params.userId, params.postId); // ✓ Type-safe  // console.log(params.invalid); // ✗ Error}

Real-world kullanımlar: Type-safe API client'lar, CSS-in-JS library'leri, i18n key validation, database query builder'lar.

Performance: Tüm computation compile time'da gerçekleşir - sıfır runtime cost.

7. Type Extraction için infer Keyword

infer keyword, kompleks generic structure'lardan type'lar extract etmeni sağlar, güçlü type-level programming'e olanak tanır.

Promise Value Type Extract Et:

typescript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // stringtype B = UnwrapPromise<number>; // number
// Async function'ları typing etmek için yararlıasync function fetchData(): Promise<{ id: number; name: string }> {  // Implementation}
type Data = UnwrapPromise<ReturnType<typeof fetchData>>;// { id: number; name: string }

Array Element Type Extract Et:

typescript
type ElementType<T> = T extends (infer U)[] ? U : T;
type Items = ElementType<string[]>; // stringtype Single = ElementType<number>; // number
// Generic array utility'leri için yararlıfunction first<T extends any[]>(arr: T): ElementType<T> | undefined {  return arr[0];}

Type-Safe API Client:

typescript
type APIResponse = {  '/users': { id: number; name: string }[];  '/posts': { id: number; title: string; body: string }[];  '/comments': { id: number; text: string; authorId: number }[];};
type FetchResult<T extends keyof APIResponse> = APIResponse[T];
async function fetchAPI<T extends keyof APIResponse>(  endpoint: T): Promise<FetchResult<T>> {  const res = await fetch(endpoint);  return res.json();}
// Type-safe kullanımconst users = await fetchAPI('/users');// type: { id: number; name: string }[]
const posts = await fetchAPI('/posts');// type: { id: number; title: string; body: string }[]

Deep Partial Utility:

typescript
type DeepPartial<T> = T extends object  ? { [P in keyof T]?: DeepPartial<T[P]> }  : T;
type Config = {  database: {    host: string;    port: number;    credentials: {      username: string;      password: string;    };  };};
type PartialConfig = DeepPartial<Config>;// Tüm property'ler recursive olarak optionalconst config: PartialConfig = {  database: {    credentials: {      username: 'admin'      // password optional    }    // host ve port optional  }};

Use case'ler: Generic utility types, library authoring, kompleks type transformation'lar.

Learning curve: Orta seviyeden ileri seviyeye, ama güç yatırıma değer.

Results

Temel Konfigürasyon

İşte tartışılan tüm güvenlik özelliklerini enable eden production-ready bir tsconfig.json:

json
{  "compilerOptions": {    // Standard strict flag'ler    "strict": true,
    // Ek strictness (strict mode'da DEĞİL!)    "noUncheckedIndexedAccess": true,    "noImplicitOverride": true,    "noPropertyAccessFromIndexSignature": true,    "exactOptionalPropertyTypes": true,
    // Modern module handling (TS 5.0+)    "verbatimModuleSyntax": true,    "moduleDetection": "force",
    // Modern JavaScript target et    "target": "ES2022",    "lib": ["ES2022", "DOM"],
    // Daha iyi import'lar    "esModuleInterop": true,    "resolveJsonModule": true,    "allowJs": true,
    // Performance    "skipLibCheck": true,    "incremental": true,
    // Kullanılmayan kod tespiti    "noUnusedLocals": true,    "noUnusedParameters": true,    "allowUnreachableCode": false,    "allowUnusedLabels": false  }}

Migration Path

Mevcut projeler için gerçekçi bir migration yaklaşımı:

Phase 1: strict mode'u Enable Et (orta codebase'ler için 1-2 hafta)

  • 30k LOC codebase'de 100-500 error bekle
  • Önce noImplicitAny'a odaklan - any'yi unknown veya proper type'larla değiştir
  • Kompleks case'ler için geçici olarak // @ts-expect-error comment'leri kullan

Phase 2: noUncheckedIndexedAccess Ekle (3-5 gün)

  • 50-200 ek error bekle
  • Çoğu fix ?. optional chaining veya if (arr[i]) guard'lar eklemek
  • Bu gerçek bug'ları yakalar - bu phase sırasında 3 production issue buldum

Phase 3: Advanced Pattern'leri Benimse (devam eden)

  • Kritik domain identifier'lar için branded type'lar tanıt
  • Switch statement'ları discriminated unions + exhaustiveness ile değiştir
  • Configuration object'leri için satisfies kullan
  • Kod refactor edildikçe gradual adoption

Ölçülebilir Sonuçlar

Bu özellikleri benimseyen TypeScript projeleriyle çalışırken, gerçekçi metrikler:

Bug Azalması: Production log'larında runtime type hatalarında %30-40 azalma (6 ay boyunca önce/sonra karşılaştırmasına göre).

Refactoring Güveni: Daha önce 2-3 günlük manuel test gerektiren kod değişiklikleri, TypeScript regression'ları hemen yakalayarak saatler içinde tamamlandı.

Code Review Verimliliği: Code review'lardaki type-related sorular yaklaşık %25 düştü, type system convention'ları otomatik olarak enforce etti.

Onboarding Impact: Yeni geliştiriciler codebase ile daha hızlı productive oldular, type'lar inline documentation görevi gördü ve yaygın hataları önledi.

Performance Considerations

Compile Time: Tüm strict option'ları enable etmek, 50k LOC codebase'de TypeScript compilation time'ı %10-20 artırdı. Bu kabul edilebilir, CI build time hala 2 dakikanın altındaydı.

Runtime: Sıfır impact - tüm type bilgisi compilation sırasında siliniyor.

IDE Performance: Modern IDE'ler (VSCode, WebStorm) bu pattern'leri 100k LOC'a kadar iyi handle etti. Bunun üzerinde, codebase'i split etmek için project reference'ları kullanmayı düşün.

Yaygın Hatalar

Hata 1: Type Assertion'ları Fazla Kullanmak

as ile type assertion type checking'i tamamen bypass eder.

typescript
// Kötü - validation yokconst user = response as User;
// İyi - önce validate etfunction isUser(obj: unknown): obj is User {  return (    typeof obj === 'object' &&    obj !== null &&    'id' in obj &&    'email' in obj  );}const user = isUser(response) ? response : null;

Hata 2: noUncheckedIndexedAccess'in Var Olduğunu Unutmak

strict: true ile bile, bu option'ı explicit enable etmedikçe indexed access güvenli değil.

Hata 3: Kompleks infer Chain'leri

Aşırı kompleks type utility'leri maintain etmek zor olur. Bunları net comment'lerle daha küçük, named type'lara böl.

typescript
// Okunması zortype Complex<T> = T extends { a: infer A extends { b: infer B } } ? B : never;
// Daha iyi - net isimlerle parçalatype ExtractA<T> = T extends { a: infer A } ? A : never;type ExtractB<T> = T extends { b: infer B } ? B : never;type Result<T> = ExtractB<ExtractA<T>>;

Önemli Çıkarımlar

  1. satisfies + as const: Hem type validation hem de literal type preservation al - configuration object'leri için temel.

  2. noUncheckedIndexedAccess'i Enable Et: Bu tek compiler option sayısız "undefined" hatasını önlüyor ve strict mode'a dahil değil.

  3. Branded types: Sıfır runtime cost, büyük type safety kazançları - ID'ler, email'ler ve validate edilmiş string'ler gibi tüm domain identifier'lar için kullan.

  4. Discriminated unions + never: Compiler-enforced exhaustiveness checking handle edilmeyen state machine case'lerini önlüyor.

  5. any yerine unknown: Sistem sınırlarında explicit type validation'ı zorla, type safety'i dramatik olarak iyileştir.

  6. Type predicates vs assertions: Optional check'ler için predicate'ler, mandatory validation için assertion'lar kullan.

  7. Template literal types: Sıfır runtime cost ile type-safe string pattern'leri enable et - API route'lar, CSS value'ları ve naming convention'ları için ideal.

Her Özelliği Ne Zaman Kullanmalı

ÖzellikEn İyi KullanımNe Zaman Kullanma
satisfiesConfig object'leri, const dataDynamic runtime data
noUncheckedIndexedAccessTüm projeler (default olmalı)Yoğun array access'li legacy kod
Branded typesDomain ID'leri, validate edilmiş string'lerSistemler arası sık dönüşüm
Discriminated unionsState machine'ler, API response'larıBasit binary state'ler (boolean kullan)
Template literalsString pattern'leri, type-safe key'lerKompleks parsing mantığı
inferLibrary kodu, tekrar kullanılabilir utility'lerTek kullanımlık type manipulation'lar
Type assertionsValidate edilmiş external dataInternal kod (proper type'lar kullan)

Implementation Checklist

  • tsconfig.json'da noUncheckedIndexedAccess'i enable et
  • any'yi unknown + type guard'larla değiştir
  • Configuration object'leri için satisfies kullan
  • Domain ID'ler için branded type'lar implement et
  • State machine'lere exhaustiveness checking ekle
  • Type assertion'ları proper validation ile değiştir
  • String pattern'ler için template literal types kullan

Bu özellikler TypeScript'i bir type annotation sisteminden güçlü bir compile-time verification tool'una dönüştürüyor. Öğrenme ve migration'daki ilk yatırım, daha az production bug'ı, daha hızlı refactoring ve daha iyi kod sürdürülebilirliği yoluyla kendini geri ödüyor.

İlgili Yazılar