---
title: 'Node.js Çalışma Zamanı'
source: 'https://academia.sh/tr/kurslar/nodejs'
language: tr
updated: '2026-08-17T04:41:38+00:00'
license: 'CC BY-SA 4.0'
lessons: 20
outcomes:
    - 'Sunucu tarafı çalışma zamanının olay döngüsü ve akış modelini kullanır'
    - 'Dosya sistemi, süreç ve ağ arayüzleriyle araç ve servis yazar'
    - 'Üretim koşulları için günlükleme, hata yönetimi ve izleme kurar'
---

# Node.js Çalışma Zamanı

Olay güdümlü sunucu çalışma zamanının süreç modeli, olay döngüsü aşamaları, akışlar ve geri basınç, dosya sistemi ve ağ arayüzleri; üretim koşulları için hata yönetimi, günlükleme ve izleme.

## Çalışma Zamanı Modeli

Tarayıcı ile sunucu çalışma zamanı arasındaki yetenek farkları, olay döngüsünün aşamaları, süreç nesnesi ve modül çözümleme kuralları.

### Tarayıcı ile Farklar

Source: https://academia.sh/tr/kurslar/nodejs/tarayici-ile-farklar

Barındıran ortam kavramı, genel nesnenin içeriği, iki modül sisteminin bir arada bulunması ve sunucu çalışma zamanının yetenek sınırları.

Eşzamansız JavaScript ve Çalışma Zamanı kursu, tek iş parçacıklı yürütme modelini ve
olay döngüsünü kurdu. Orada anlatılan her şey dilin kendisine değil, dili barındıran
ortama aitti: görev kuyruğu da, zamanlayıcılar da dil standardında tanımlı değildir.
Bu kurs aynı dili farklı bir barındıran ortamda ele alır.

Soru şudur: aynı sözdizim, aynı değer modeli ve aynı olay döngüsü kavramı, tarayıcı
yerine bir sunucu sürecinde çalıştığında ne değişir? Yanıt iki başlıkta toplanır —
hangi adların hazır bulunduğu ve sürecin ne yapmaya yetkili olduğu.

#### Dil Çekirdeği ve Barındıran Ortam

Dil standardı, sözdizimi ve değer modelini tanımlar: `Array`, `Map`, `Promise`,
`JSON`, `Math` bu katmandadır. Standart, bu değerlerin nasıl davranacağını söyler;
onlarla dış dünyaya nasıl erişileceğini söylemez.

**Barındıran ortam (host environment)**, dili çalıştıran programdır ve dış dünyayla
teması o sağlar. Tarayıcı bir barındıran ortamdır: belge ağacını, çerezleri, ekran
olaylarını sunar. Sunucu çalışma zamanı da bir barındıran ortamdır: dosya sistemini,
süreç bilgisini, ağ dinlemeyi sunar.

Ayrım şöyle sınanabilir. Aşağıdaki dosya, bir dizi adın genel nesnede bulunup
bulunmadığını yazar:

```js
// ortam.mjs
const adlar = [
  'window', 'document', 'localStorage', 'alert',
  'process', 'Buffer', 'global',
  'fetch', 'URL', 'TextEncoder', 'AbortController', 'structuredClone',
];
for (const ad of adlar) {
  console.log(`${ad.padEnd(16)} ${ad in globalThis ? 'var' : 'yok'}`);
}
```

```sh
node ortam.mjs
```

```
window           yok
document         yok
localStorage     yok
alert            yok
process          var
Buffer           var
global           var
fetch            var
URL              var
TextEncoder      var
AbortController  var
structuredClone  var
```

Çıktı üç öbek gösterir. Birinci öbek yalnızca tarayıcıda bulunur: belge ağacı, sayfa
deposu ve kullanıcı arayüzü çağrıları sunucuda karşılıksızdır. İkinci öbek yalnızca
sunucu tarafında bulunur: `process` süreç bilgisini, `Buffer` ham baytları temsil eder.

Üçüncü öbek daha ilginçtir. `fetch`, `URL`, `TextEncoder`, `AbortController` ve
`structuredClone` tarayıcı için tanımlanmış arayüzlerdir, ancak dile değil web
platformuna aittir. Sunucu çalışma zamanı bunların bir bölümünü aynı sözleşmeyle
gerçekleştirir; böylece iki ortamda çalışan kod yazmak, her arayüzün iki ayrı
sürümünü tutmayı gerektirmez. Bu ortaklık genişleyen bir kümedir: bir arayüzün
belirli bir sürümden itibaren bulunduğu belgelendirilmişse ona güvenilebilir,
bulunmadığı ortamlar için ise varlık denetimi yapılır.

`global` adı, `globalThis` ile aynı nesneyi gösteren eski bir addır. Yeni kodda
`globalThis` yazmak, aynı ifadenin her iki ortamda da çalışmasını sağlar.

#### İki Modül Sistemi

Tarayıcıda tek bir modül biçimi vardır. Sunucu çalışma zamanı ise iki biçimi bir
arada taşır ve her dosya için birini seçer.

**CommonJS**, `require` çağrısıyla eşzamanlı yükleme yapan, `module.exports` nesnesine
yazarak dışa aktaran biçimdir. **ES modülleri**, `import` ve `export` bildirimleriyle
çalışan, bağımlılıkları çalıştırmadan önce çözen biçimdir. İkisinin farkı Modüller,
Araçlar ve Ekosistem kursunda ayrıntılandırılmıştı; burada önemli olan, seçimin nasıl
yapıldığıdır.

Seçim iki kurala bakar: dosya uzantısı ve en yakın paket bildirimi. `.cjs` uzantılı
dosya her zaman CommonJS, `.mjs` uzantılı dosya her zaman ES modülüdür. `.js`
uzantısında karar, dosyanın bulunduğu dizinden yukarı doğru bulunan ilk
`package.json` dosyasındaki `type` alanına göre verilir; alan `module` ise ES modülü,
`commonjs` ise veya alan yoksa CommonJS.

İki biçimin çalışma anındaki farkı ölçülebilir:

```js
// kapsam.cjs
console.log('this === module.exports :', this === module.exports);
console.log('__filename son parca   :', __filename.split('/').pop());
console.log('require turu           :', typeof require);
```

```js
// kapsam.mjs
console.log('this                   :', this);
console.log('import.meta.url son parca:', import.meta.url.split('/').pop());
console.log('require turu           :', typeof require);
```

```sh
node kapsam.cjs
```

```
this === module.exports : true
__filename son parca   : kapsam.cjs
require turu           : function
```

```sh
node kapsam.mjs
```

```
this                   : undefined
import.meta.url son parca: kapsam.mjs
require turu           : undefined
```

CommonJS dosyası bir fonksiyon gövdesi gibi sarmalanır: `require`, `module`,
`__filename` ve `__dirname` adları o sarmalayıcının parametreleridir, genel değildir.
ES modülünde bu adlar yoktur; dosyanın kendi konumu `import.meta.url` üzerinden, bir
dosya yolu olarak değil bir adres olarak verilir. Kurs boyunca ES modülleri
kullanılacak; CommonJS yalnızca farkın görülmesi gereken yerlerde geçecek.

#### Yetenek ve Yetki

Asıl fark ad listesinde değil, sürecin ne yapabildiğindedir.

Tarayıcıda çalışan kod, kullanıcının makinesine karşı düşman kabul edilir ve bir kum
havuzunda tutulur: keyfi bir dosyayı okuyamaz, keyfi bir bağlantı noktasını
dinleyemez, başka bir kaynağa isteği ancak o kaynağın izin verdiği ölçüde atabilir.
Bu kısıtlar tarayıcının kullanıcıya verdiği güvencelerdir.

Sunucu çalışma zamanında böyle bir kum havuzu yoktur. Süreç, onu başlatan kullanıcının
yetkileriyle çalışır. Linux'a Giriş kursunda kurulan izin modeli burada doğrudan
geçerlidir: süreç hangi dosyaları okuyabiliyorsa çalıştırdığınız betik de onları
okuyabilir. Aynı kural yazma ve silme için de geçerlidir.

Bu yetki, aşağıdaki kısa dosyada görülür:

```js
// ilk-okuma.mjs
import { readFileSync } from 'node:fs';

const metin = readFileSync('olcumler.ndjson', 'utf8');
const satirlar = metin.trim().split('\n');

console.log('satir sayisi:', satirlar.length);
console.log('ilk kayit   :', JSON.parse(satirlar[0]));
```

Modül adındaki `node:` öneki, adın bir yerleşik modülü gösterdiğini bildirir. Önek
olmadan da çözümlenir; önekli yazım, aynı adda bir dosya veya paket bulunması
durumunda hangisinin kastedildiğini kesinleştirir. Kurs boyunca önekli biçim
kullanılacak.

#### Kursun Çalışma Dosyası

Bu kurs boyunca tek bir somut iş geliştirilecek: uzaktaki ölçüm düğümlerinden gelen
kayıtları toplayan, özetleyen ve HTTP üzerinden sunan küçük bir hizmet. Girdi, her
satırı bağımsız bir JSON nesnesi olan bir metin dosyasıdır. Aşağıdaki komut, on iki satırı
`olcumler.ndjson` adıyla çalışma dizininde oluşturur:

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

Her satırdaki dört alan sırasıyla ölçüm anını, ölçümü gönderen düğümü, ölçülen
büyüklüğü ve değeri taşır. Satır başına bir kayıt tutan bu biçim, Kabuk Programlama
kursundaki satır tabanlı süzgeçlerle uyumludur: dosyanın tamamını belleğe almadan,
satır satır işlenebilir. Bu özellik akışlar konusunda belirleyici olacak.

Dosyayı yerine koyduktan sonra ilk okumayı çalıştırın:

```sh
node ilk-okuma.mjs
```

```
satir sayisi: 12
ilk kayit   : {
  zaman: '2024-02-07T09:12:44Z',
  dugum: 'kenar-01',
  metrik: 'sicaklik',
  deger: 21.4
}
```

Bu çıktı, tarayıcıda karşılığı olmayan bir işi gösterir: yerel bir dosya, kullanıcı
onayı olmadan, adı verilerek okundu. Kursun geri kalanı bu yeteneğin üzerine kurulur —
ve aynı yeteneğin getirdiği sorumluluğu da işler.

Dosya bulunamadığında ortaya çıkan hata, kurs boyunca sık göreceğiniz biçimdedir:

```sh
node -e "import('node:fs').then(m => m.readFileSync('yok.ndjson','utf8')).catch(e => console.log(e.code, '|', e.syscall, '|', e.path))"
```

```
ENOENT | open | yok.ndjson
```

`ENOENT`, işletim sisteminin sistem çağrısından döndürdüğü hata adıdır; `syscall`
hangi çağrının başarısız olduğunu, `path` hangi yol için olduğunu söyler. Bu üç alan,
hata iletisinin metnine bakmadan karar vermenizi sağlar — hata metni değişebilir,
kod değişmez.

#### Özet

- Dil standardı değer modelini tanımlar; dış dünyaya erişim barındıran ortamın
  sorumluluğudur. Tarayıcı ve sunucu çalışma zamanı iki ayrı barındıran ortamdır.
- Genel nesnede üç öbek vardır: yalnızca tarayıcıda bulunanlar, yalnızca sunucuda
  bulunanlar ve web platformundan devralınan ortak arayüzler.
- Modül biçimi dosya uzantısıyla (`.cjs`, `.mjs`) veya en yakın paket bildirimindeki
  `type` alanıyla seçilir; CommonJS dosyası sarmalanır ve `require`, `module`,
  `__filename` adlarını parametre olarak alır.
- Sunucu sürecinde kum havuzu yoktur; süreç, kendisini başlatan kullanıcının
  dosya ve ağ yetkileriyle çalışır.
- Yerleşik modüller `node:` önekiyle yazıldığında ad çakışması olasılığı ortadan
  kalkar; dosya sistemi hataları `code`, `syscall` ve `path` alanlarıyla ayırt edilir.

#### Sonraki Adım

Bu derste dosya `readFileSync` ile, yani süreci bekleterek okundu. Tek iş parçacıklı
bir sunucuda bekletmenin bedeli ağırdır: bekleyen süre boyunca başka hiçbir istek
işlenemez. Sonraki ders, bu bedeli ödememek için kullanılan düzeneği aşama aşama
açar — olay döngüsünün hangi sırayla hangi kuyruğu boşalttığını, zamanlayıcıların ne
zaman çalıştığını ve girdi/çıktı geri çağrılarının bu sıraya nereden girdiğini
ölçülebilir örneklerle gösterir.

### Olay Döngüsü Aşamaları

Source: https://academia.sh/tr/kurslar/nodejs/olay-dongusu-asamalari

Döngünün aşama sırası, zamanlayıcı ile denetim aşaması arasındaki fark, iki mikro görev kuyruğunun boşaltılma anı ve engelleyen işin ölçülebilir bedeli.

Önceki ders dosyayı `readFileSync` ile okudu: çağrı döndüğünde veri hazırdı, ama çağrı
dönene kadar süreçte başka hiçbir iş ilerlemedi. Tek iş parçacıklı bir sunucuda bu
davranış kabul edilebilir değildir; bekleyen her milisaniye, aynı anda gelen bütün
isteklerin gecikmesidir.

Bu dersin sorusu şudur: bekleme yerine geçen düzenek tam olarak ne yapar? Eşzamansız
JavaScript ve Çalışma Zamanı kursunda olay döngüsü, çağrı yığıtı boşaldığında kuyruktan
görev alan bir çevrim olarak tanıtılmıştı. Sunucu çalışma zamanında bu çevrimin
içi görünür: tek bir kuyruk değil, sırayla ziyaret edilen birkaç aşama vardır.

#### Döngünün Aşamaları

Süreç, ana modülün gövdesini baştan sona çalıştırdıktan sonra olay döngüsüne girer.
Döngünün her turu, sabit sırada birkaç aşamadan geçer. Her aşamanın kendi geri çağrı
kuyruğu vardır; aşamaya girildiğinde o kuyruk boşaltılır, sonra bir sonraki aşamaya
geçilir.

| Aşama | Çalıştırdığı geri çağrılar |
|---|---|
| zamanlayıcı | süresi dolmuş `setTimeout` ve `setInterval` geri çağrıları |
| bekleyen geri çağrılar | bir önceki turdan ertelenmiş sistem düzeyi geri çağrılar |
| yoklama | tamamlanan girdi/çıktı işlemlerinin geri çağrıları |
| denetim | `setImmediate` ile kurulan geri çağrılar |
| kapanma | `close` olaylarının geri çağrıları |

**Yoklama (poll)** aşaması, döngünün asıl beklediği yerdir. Çalıştırılacak başka iş
yoksa süreç burada durur ve işletim sisteminden "hazır olan girdi/çıktı var mı?" diye
sorar. Bu bekleme, işlemciyi meşgul eden bir bekleme değildir; çekirdek, bir soket
okunabilir hâle geldiğinde ya da bir dosya okuması bittiğinde süreci uyandırır.

Böylece "beklemek" ile "engellemek" ayrışır. Süreç bir isteğin yanıtını beklerken
yoklama aşamasında uyuyor olabilir; bu sırada başka bir bağlantıdan veri gelirse
uyanır ve onun geri çağrısını çalıştırır.

#### Zamanlayıcı ile Denetim Aşamasının Sırası

Bir aşamanın kuyruğuna, o aşamadan sonra gelen bir aşamanın içindeyken ekleme
yapılırsa, geri çağrı aynı turda çalışır; önce gelen bir aşamanın kuyruğuna ekleme
yapılırsa sonraki turu bekler. Bu kural, girdi/çıktı geri çağrısının içinden bakınca
kesin bir sıra üretir:

```js
// asamalar.mjs
import { readFile } from 'node:fs';

console.log('senkron govde');

readFile('olcumler.ndjson', () => {
  console.log('yoklama: dosya okundu');

  process.nextTick(() => console.log('  nextTick kuyrugu'));
  Promise.resolve().then(() => console.log('  soz mikro gorevi'));
  setTimeout(() => console.log('  zamanlayici asamasi'), 0);
  setImmediate(() => console.log('  denetim asamasi'));

  console.log('yoklama: geri cagri govdesi bitti');
});
```

```sh
node asamalar.mjs
```

```
senkron govde
yoklama: dosya okundu
yoklama: geri cagri govdesi bitti
  nextTick kuyrugu
  soz mikro gorevi
  denetim asamasi
  zamanlayici asamasi
```

Sıra rastlantı değildir. Geri çağrı yoklama aşamasında çalıştı; denetim aşaması
yoklamadan **sonra** geldiği için `setImmediate` aynı turda çalıştı. Zamanlayıcı
aşaması ise yoklamadan **önce** geldiğinden `setTimeout` bir sonraki turu bekledi.
Bu ilişki, girdi/çıktı geri çağrısı içinde her zaman böyledir.

Aynı iki çağrı ana modülün gövdesinden yapıldığında sıra güvence altında değildir:

```js
// yaris.mjs
setTimeout(() => console.log('zamanlayici'), 0);
setImmediate(() => console.log('denetim'));
```

Sıra çalıştırmadan çalıştırmaya değiştiği için aşağıdaki iki blok ölçülmez; çıktılar,
aynı dosyanın ardışık çalıştırmalarından alınmış örnek koşumlardır:

{/* run: skip */}
```sh
node yaris.mjs
```

```
denetim
zamanlayici
```

{/* run: skip */}
```sh
node yaris.mjs
```

```
zamanlayici
denetim
```

Gerekçe, döngünün ilk turuna girildiğinde geçen sürededir. `setTimeout` için verilen
sıfır, çalışma zamanı tarafından en az bir milisaniyeye yuvarlanır. Ana modülün
çalışması bir milisaniyeden kısa sürerse döngü zamanlayıcı aşamasına girdiğinde süre
henüz dolmamıştır ve geri çağrı bir sonraki tura kalır; uzun sürerse süre dolmuş olur
ve geri çağrı ilk turda çalışır. Süre makinenin yüküne bağlı olduğundan sonuç
çalıştırmadan çalıştırmaya değişir. Buradan çıkan kural şudur: **iki farklı aşamaya
ait geri çağrıların göreli sırasına program mantığı bağlanmaz.**

#### İki Mikro Görev Kuyruğu

Aşamalar arasında ve her geri çağrıdan sonra, iki ek kuyruk boşaltılır. Bunlar bir
aşamaya ait değildir; aşamalar arasındaki her denetim noktasında ele alınırlar.

- **`process.nextTick` kuyruğu**, çalışma zamanına özgüdür.
- **Söz mikro görev kuyruğu**, dilin standardında tanımlıdır ve tarayıcıda da bulunur.

Bir denetim noktasında önce `nextTick` kuyruğu, sonra söz kuyruğu boşaltılır.
Yukarıdaki çıktıda bu sıra görülüyor: geri çağrı gövdesi bittikten sonra önce
`nextTick kuyrugu`, ardından `soz mikro gorevi` yazıldı.

Bu sıranın modülün en üst düzeyinde farklı göründüğü bir durum vardır:

```js
// mikro.cjs
console.log('1 senkron');
process.nextTick(() => console.log('2 nextTick'));
Promise.resolve().then(() => console.log('3 mikro gorev'));
console.log('4 senkron son');
```

```sh
node mikro.cjs
```

```
1 senkron
4 senkron son
2 nextTick
3 mikro gorev
```

Aynı satırlar `.mjs` uzantısıyla kaydedildiğinde sıra tersine döner:

```js
// mikro.mjs
console.log('1 senkron');
process.nextTick(() => console.log('2 nextTick'));
Promise.resolve().then(() => console.log('3 mikro gorev'));
console.log('4 senkron son');
```

```sh
node mikro.mjs
```

```
1 senkron
4 senkron son
3 mikro gorev
2 nextTick
```

Nedeni, ES modüllerinin değerlendirilme biçimidir: modül gövdesinin kendisi bir söz
işi olarak çalışır. Gövde bittiğinde süreç hâlâ söz kuyruğunu boşaltmaktadır, bu
yüzden yeni eklenen söz işi aynı boşaltma turunda çalışır; `nextTick` kuyruğu ise
turun sonundaki denetim noktasını bekler. Ders şu: iki kuyruğun göreli sırası bir
sözleşme değildir, kodun nerede çalıştığına bağlıdır. Programın doğruluğu bu sıraya
dayanmamalıdır.

`process.nextTick` kuyruğunun ayrıca bir tehlikesi vardır. Bu kuyruk, bir sonraki
aşamaya geçilmeden **tamamen** boşaltılır; bir geri çağrı her çalıştığında kuyruğa
yenisini eklerse döngü hiçbir zaman ilerleyemez ve süreç yanıt vermeyi bırakır.

#### Engellemenin Ölçülen Bedeli

Aşama modelinin tek bir zayıf noktası vardır: bir aşamanın geri çağrısı çalışırken
başka hiçbir şey çalışamaz. Uzun süren senkron iş, döngünün tamamını durdurur.

```js
// engelleme.mjs
const baslangic = process.hrtime.bigint();

setTimeout(() => {
  const gecen = Number(process.hrtime.bigint() - baslangic) / 1e6;
  console.log(`10 ms icin kurulan zamanlayici ${gecen.toFixed(0)} ms sonra calisti`);
}, 10);

// Olay dongusunu bilerek mesgul eden senkron is
const bitis = Date.now() + 200;
while (Date.now() < bitis) { /* bos dongu */ }
console.log('senkron is bitti');
```

```sh
node engelleme.mjs
```

{/* mask: (?<=zamanlayici )2[0-9]{2}(?= ms sonra) */}
```
senkron is bitti
10 ms icin kurulan zamanlayici 202 ms sonra calisti
```

Yazdırılan süre makineye ve yüke göre birkaç milisaniye oynar; belirleyici olan
büyüklüktür. Zamanlayıcı için verilen 10 ms bir söz değil bir **alt sınırdır**: geri
çağrı, süresi dolduktan sonra döngü zamanlayıcı aşamasına ulaştığında çalışır. Döngü
200 ms boyunca meşgul edildiği için gecikme de yaklaşık 200 ms olmuştur.

Aynı ölçü bir sunucuda şu anlama gelir: her istek başına 200 ms senkron iş yapan bir
işleyici, saniyede en çok beş isteğe yanıt verir ve altıncı istek kuyrukta bekler.
Bu sınır donanımdan değil, işin yürütülme biçiminden gelir.

Buradan iki kural çıkar. Birincisi, girdi/çıktı için eşzamanlı arayüzler (`...Sync`
ile biten çağrılar) sunucu isteği işlerken kullanılmaz; başlangıç anındaki
yapılandırma okuması gibi, döngü henüz çalışmıyorken yapılan işler ayrıcalıklıdır.
İkincisi, işlemciye bağlı uzun hesaplar ana iş parçacığından çıkarılmalıdır; bunun
yolu Küme ve İşçi İş Parçacıkları dersinde ele alınacak.

#### Özet

- Olay döngüsünün her turu sabit sırada aşamalardan geçer: zamanlayıcı, bekleyen geri
  çağrılar, yoklama, denetim, kapanma. Her aşamanın kendi kuyruğu vardır.
- Yoklama aşaması, işlemciyi meşgul etmeyen bekleme noktasıdır; çekirdek, hazır olan
  girdi/çıktı için süreci uyandırır.
- Girdi/çıktı geri çağrısı içinde `setImmediate` aynı turda, `setTimeout` bir sonraki
  turda çalışır. Ana modül gövdesinde bu sıra güvence altında değildir.
- `nextTick` ve söz kuyrukları aşamalar arasındaki denetim noktalarında boşaltılır;
  göreli sıraları kodun nerede çalıştığına bağlıdır ve program mantığına dayanak
  yapılmaz.
- Zamanlayıcı gecikmesi alt sınırdır; döngüyü meşgul eden senkron iş, gecikmeyi
  doğrudan kendi süresi kadar büyütür.

#### Sonraki Adım

Bu derste süreç, döngüyü çalıştıran bir kap olarak ele alındı. Sürecin kendisi de
programa açık bir nesnedir: hangi argümanlarla başlatıldığını, hangi ortam
değişkenlerini devraldığını, standart akışlarının nereye bağlı olduğunu ve hangi
çıkış koduyla sonlanacağını bu nesne üzerinden okur ve yazarsınız. Sonraki ders
süreç nesnesini ele alır ve ölçüm toplayıcının ilk komut satırı davranışını kurar.

### Süreç Nesnesi

Source: https://academia.sh/tr/kurslar/nodejs/surec-nesnesi

Argüman dizisi, ortam değişkenleri, standart akışların nesne karşılığı, çıkış kodu belirlemenin iki yolu ve çıkış kancası.

Önceki ders döngünün turlarını inceledi. Turların içinde çalışan program, dış dünyayla
yalnızca dosya ve ağ üzerinden değil, kendisini başlatan ortamla da konuşur: hangi
argümanlarla çağrıldı, hangi ortam değişkenlerini devraldı, çıktısı nereye bağlı ve
sonlandığında çağırana ne bildirecek?

Bu bilgilerin tamamı tek bir genel nesnede toplanır. Kabuk Programlama kursunda bir
betiğin `$1`, `$?` ve `$HOME` ile yaptığı şeylerin karşılığı burada bu nesnenin
alanlarıdır.

#### Argümanlar ve Ortam

`process.argv`, sürecin başlatıldığı komut satırını bir dizgi dizisi olarak taşır.
Dizinin ilk iki öğesi sabittir: çalıştırılabilir dosyanın yolu ve çalıştırılan ana
modülün yolu. Kullanıcının yazdığı argümanlar üçüncü öğeden başlar.

```js
// surec.mjs
console.log('argv[0] son parca:', process.argv[0].split('/').pop());
console.log('argv[1] son parca:', process.argv[1].split('/').pop());
console.log('kalan argumanlar :', process.argv.slice(2));
console.log('platform         :', process.platform);
console.log('calisma dizini   :', process.cwd().split('/').pop());
console.log('OLCUM_DIZINI     :', process.env.OLCUM_DIZINI ?? '(tanimsiz)');
```

```sh
node surec.mjs --dugum kenar-01 --bicim ozet
```

{/* mask: (?<=calisma dizini   : )[A-Za-z0-9_-]+ */}
```
argv[0] son parca: node
argv[1] son parca: surec.mjs
kalan argumanlar : [ '--dugum', 'kenar-01', '--bicim', 'ozet' ]
platform         : linux
calisma dizini   : lab
OLCUM_DIZINI     : (tanimsiz)
```

İlk iki satırdaki tam yollar makineye göre değişir; bu yüzden örnekte yalnızca son
parçaları yazdırılıyor. `platform` alanı çalıştığınız işletim sistemine göre farklı
bir değer verir (örnekte `linux`); `calisma dizini` ise komutu hangi dizinden çağırdığınıza bağlıdır.
Yolları sabitlemenin doğru yolu bir sonraki konuda ele alınacak.

Ortam değişkenleri `process.env` üzerinden okunur. Bu nesnenin bütün değerleri
dizgidir; sayı bekliyorsanız dönüştürmek ve doğrulamak size düşer.

```sh
OLCUM_DIZINI=/veri/olcum node surec.mjs
```

{/* mask: (?<=calisma dizini   : )[A-Za-z0-9_-]+ */}
```
argv[0] son parca: node
argv[1] son parca: surec.mjs
kalan argumanlar : []
platform         : linux
calisma dizini   : lab
OLCUM_DIZINI     : /veri/olcum
```

Değişkenin komuttan önce yazılması, onu yalnızca o çağrı için tanımlar; kabuk oturumu
etkilenmez. Bu, Kabuk Programlama kursunda kurulan ortam devri kuralının aynısıdır.

#### Standart Akışların Nesne Karşılığı

Kabuk Programlama kursunda üç standart akış birer numarayla anılmıştı: 0 girdi,
1 çıktı, 2 hata. Çalışma zamanı bu üç tanıtıcıyı birer nesne olarak sunar:
`process.stdin`, `process.stdout` ve `process.stderr`. Bunlar sıradan
nesneler değil, ilerleyen derslerde ayrıntılandırılacak akış nesneleridir; şimdilik
`write` yöntemleri yeterlidir.

Akış ayrımı kuralı burada da geçerlidir: verinin kendisi standart çıktıya, veriyle
ilgili her açıklama standart hataya yazılır. Aşağıdaki araç ölçüm dosyasını okuyup
sekmeyle ayrılmış iki sütun yazar; sayaç ve hata iletileri ikinci akışa gider.

```js
// akis-ayrimi.mjs
import { readFileSync } from 'node:fs';

const dosya = process.argv[2] ?? 'olcumler.ndjson';

let metin;
try {
  metin = readFileSync(dosya, 'utf8');
} catch (hata) {
  process.stderr.write(`hata: ${dosya} okunamadi (${hata.code})\n`);
  process.exitCode = 66;                    // veri girdisi yok
  metin = '';
}

let sayac = 0;
for (const satir of metin.split('\n')) {
  if (satir.trim() === '') continue;
  const { dugum, deger } = JSON.parse(satir);
  process.stdout.write(`${dugum}\t${deger}\n`);
  sayac += 1;
}

process.stderr.write(`${sayac} kayit islendi\n`);

process.on('exit', (kod) => {
  process.stderr.write(`surec ${kod} koduyla sonlaniyor\n`);
});
```

Araç, ilk derste oluşturulan `olcumler.ndjson` dosyasını okur. Dosya çalışma
dizininizde yoksa şu komutla yeniden oluşturun:

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

Yalnızca veriyi görmek için ikinci akış ıskartaya çıkarılır:

```sh
node akis-ayrimi.mjs 2>/dev/null | head -3
```

```
kenar-01	21.4
kenar-01	48
kenar-02	19.8
```

Yalnızca tanı iletilerini görmek için birinci akış:

```sh
node akis-ayrimi.mjs 1>/dev/null
```

```
12 kayit islendi
surec 0 koduyla sonlaniyor
```

Bu ayrım, aracın bir boru hattına takılabilmesinin ön koşuludur. Sayaç satırı standart
çıktıya yazılsaydı, çıktıyı okuyan bir sonraki komut onu veri satırı sanardı.

`console.log` çağrısı standart çıktıya, `console.error` standart hataya yazar. Bunlar
`write` üzerine kurulmuş kolaylık fonksiyonlarıdır; nesneleri okunabilir biçimde
düzenlemeleri yararlıdır, ancak çıktısı başka bir programa girdi olacak yerlerde
biçimi doğrudan denetlemek için `write` yeğlenir.

#### Çıkış Kodu

Bir sürecin çağırana bildirdiği tek sayısal sonuç çıkış kodudur. Kabuk Programlama
kursundaki kural burada da geçerlidir: sıfır başarı, sıfırdan farklı her değer bir
başarısızlık türüdür.

Kodu belirlemenin iki yolu vardır ve aralarındaki fark önemlidir.

```js
// cikis-sert.mjs
setTimeout(() => console.log('bekleyen is'), 0);
console.log('cikis oncesi');
process.exit(3);
```

```sh
node cikis-sert.mjs; echo "cikis kodu: $?"
```

```
cikis oncesi
cikis kodu: 3
```

```js
// cikis-yumusak.mjs
setTimeout(() => console.log('bekleyen is'), 0);
console.log('cikis oncesi');
process.exitCode = 3;
```

```sh
node cikis-yumusak.mjs; echo "cikis kodu: $?"
```

```
cikis oncesi
bekleyen is
cikis kodu: 3
```

`process.exit`, olay döngüsünü o anda durdurur; kuyrukta bekleyen geri çağrılar
çalışmaz. Yukarıdaki ilk çıktıda `bekleyen is` satırının olmaması bunun sonucudur.
Aynı kesme, henüz aktarılmamış çıktı için de geçerli olabilir: çıktısı bir boruya
bağlı bir süreçte yazma işlemi eşzamansız yürütülebilir ve sert çıkış onu yarıda
bırakabilir. Dosyaya yazma eşzamanlıdır ve bu kesintiden etkilenmez.

`process.exitCode` alanına yazmak ise yalnızca sonucu kaydeder. Döngü işini bitirip
doğal olarak boşaldığında süreç bu kodla sonlanır. Bekleyen yazma, kapanmakta olan
bağlantı ve süren dosya işlemi tamamlanır.

Kural: **sert çıkış yalnızca sürecin durumunun bozulduğuna karar verildiğinde
kullanılır.** Olağan hata yollarında kod alana yazılır ve süreç kendiliğinden biter.

Yukarıdaki araç bu kuralı izler. Dosya bulunamadığında sürece 66 kodu verilir, ama
süreç kesilmez; sayaç satırı ve çıkış kancası yine çalışır:

```sh
node akis-ayrimi.mjs yok.ndjson; echo "cikis kodu: $?"
```

```
hata: yok.ndjson okunamadi (ENOENT)
0 kayit islendi
surec 66 koduyla sonlaniyor
cikis kodu: 66
```

#### Çıkış Kancası

`process.on('exit', ...)` ile kurulan geri çağrı, süreç sonlanmadan hemen önce
çalışır ve çıkış kodunu argüman olarak alır. Yukarıdaki her iki çalıştırmada son
satırı yazan budur.

Bu kancanın tek katı kuralı vardır: içinde **eşzamansız iş başlatılamaz.** Süreç
kapanmak üzeredir; kancadan sonra olay döngüsüne dönülmez, bu yüzden kancada kurulan
bir zamanlayıcı veya dosya yazımı hiçbir zaman çalışmaz. Kanca yalnızca eşzamanlı,
kısa işler içindir — bir sayaç yazmak, bir kilidi eşzamanlı olarak kaldırmak gibi.

Gerçek temizlik işleri, kapanma **sinyali** alındığında yapılır; sinyaller ve zarif
kapanma Süreç Yönetimi ve Sağlamlık dersinin konusudur.

Sürecin kendini tanıtan iki alanı daha vardır. `process.pid`, işletim sisteminin
sürece verdiği kimliktir ve her çalıştırmada farklıdır; kütük satırlarında birden
çok süreci ayırt etmek için kullanılır. `process.uptime()`, sürecin başlamasından
bu yana geçen saniyeyi verir ve sağlık uçlarında yaş bilgisi olarak yazılır.

#### Özet

- `process.argv` dizisinin ilk iki öğesi çalıştırılabilir dosya ve ana modül yoludur;
  kullanıcı argümanları üçüncü öğeden başlar.
- `process.env` değerleri her zaman dizgidir; sayısal veya mantıksal yorum, okuyan
  kodun sorumluluğundadır.
- Üç standart akış birer nesne olarak sunulur; veri standart çıktıya, tanı iletisi
  standart hataya yazıldığında araç bir boru hattına takılabilir hâle gelir.
- `process.exit` döngüyü o anda keser ve bekleyen işleri düşürür; `process.exitCode`
  yalnızca sonucu kaydeder ve sürecin doğal olarak bitmesine izin verir.
- `exit` kancası yalnızca eşzamanlı iş için uygundur; kancadan sonra olay döngüsüne
  dönülmediğinden eşzamansız temizlik burada yapılamaz.

#### Sonraki Adım

Bu derste dosya adı `olcumler.ndjson` biçiminde, çalışma dizinine göre verildi;
program başka bir dizinden çağrıldığında dosyayı bulamaz. Sonraki ders bu bağımlılığı
ortadan kaldıran düzeneği ele alır: bir ad yazıldığında çalışma zamanının hangi
dosyayı yükleyeceğine hangi kurallarla karar verdiğini — yerleşik modül, dosya yolu ve
paket adı arasındaki ayrımı — inceler.

### Modül Çözümleme

Source: https://academia.sh/tr/kurslar/nodejs/modul-cozumleme

Üç tanımlayıcı türü, dosya uzantısı zorunluluğu, paket dizini arama zinciri, dışa aktarım haritası ve çözümlemenin programdan sorgulanması.

Önceki ders sürecin dış dünyayla nasıl konuştuğunu kurdu. Bu dersin sorusu daha
dardır ama etkisi geniştir: `import` satırında yazan ad, hangi dosyaya karşılık gelir?

Yanıt üç ayrı kural kümesine dağılmıştır. Yazdığınız tanımlayıcının hangi kümeye
girdiği ilk karakterinden anlaşılır ve seçim geri kalan her şeyi belirler.

#### Üç Tanımlayıcı Türü

**Yerleşik tanımlayıcı**, `node:` önekiyle başlar: `node:fs`, `node:path`,
`node:http`. Çalışma zamanının içine gömülü modülleri gösterir; dosya sisteminde
aranmaz.

**Dosya tanımlayıcısı**, `./`, `../` veya bir kök yol ile başlar. İçinde bulunduğu
dosyanın konumuna göre çözülür.

**Paket tanımlayıcısı**, bu iki biçime de uymayan addır: `olcum-araclari`,
`olcum-araclari/birim`. Paket dizinlerinde aranır.

Üçü de tek bir dosyada yan yana kullanılabilir. Aşağıdaki düzen kurs boyunca
tekrarlanacak bir yapıdır: bir paket bildirimi, bir yerel modül ve bağımlılık
dizinindeki bir paket.

```
cozumleme/
  package.json
  ana.js
  ozet.js
  node_modules/
    olcum-araclari/
      package.json
      index.js
      birim.js
      ic.js
```

Kaynak dosyalar, ilk satırdaki yorumda yazan yolla verilmiştir. İki `package.json`
dosyası ise komutla oluşturulur:

```sh
mkdir -p cozumleme/node_modules/olcum-araclari
cat > cozumleme/package.json <<'SON'
{
  "name": "olcum-toplayici",
  "version": "1.0.0",
  "type": "module"
}
SON
cat > cozumleme/node_modules/olcum-araclari/package.json <<'SON'
{
  "name": "olcum-araclari",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": "./index.js",
    "./birim": "./birim.js"
  }
}
SON
```

```js
// cozumleme/node_modules/olcum-araclari/index.js
export const ad = 'olcum-araclari';
```

```js
// cozumleme/node_modules/olcum-araclari/birim.js
export const birimler = { sicaklik: 'C', nem: '%' };
```

```js
// cozumleme/node_modules/olcum-araclari/ic.js
export const gizli = true;
```

```js
// cozumleme/ozet.js
export function ortalama(sayilar) {
  return sayilar.reduce((t, s) => t + s, 0) / sayilar.length;
}
```

```js
// cozumleme/ana.js
import { readFileSync } from 'node:fs';
import { ortalama } from './ozet.js';
import { ad } from 'olcum-araclari';
import { birimler } from 'olcum-araclari/birim';

console.log('yerleşik modül  :', typeof readFileSync);
console.log('dosya modülü    :', ortalama([21.4, 19.8, 24.1]).toFixed(2));
console.log('paket modülü    :', ad);
console.log('paket alt yolu  :', birimler.sicaklik);
```

```sh
node cozumleme/ana.js
```

```
yerleşik modül  : function
dosya modülü    : 21.77
paket modülü    : olcum-araclari
paket alt yolu  : C
```

Dört dosyanın uzantısı `.js`; ES modülü olarak yorumlanmalarını sağlayan, en yakın
paket bildirimindeki `"type": "module"` alanıdır. Bağımlılık dizinindeki paketin
kendi bildirimi de aynı alanı taşır — bir paketin modül biçimi kendi bildiriminden
gelir, onu kullananın bildiriminden değil.

#### Dosya Tanımlayıcısında Uzantı Zorunludur

CommonJS'te `require('./ozet')` yazmak yeterliydi; çalışma zamanı sırayla
`./ozet.js`, `./ozet.json`, `./ozet.node` adlarını dener, bulamazsa `./ozet/`
dizinindeki dizin dosyasına bakardı. Bu tahmin, her içe aktarımda birkaç dosya
sistemi çağrısı demektir ve aynı ad için farklı ortamlarda farklı dosya
seçilebilmesi anlamına gelir.

ES modüllerinde tahmin yoktur. Parantez, `cd` komutunun yalnızca o satırı
etkilemesini sağlar:

```sh
(cd cozumleme && node -e "import('./ozet').catch(e => console.log(e.code))")
```

```
ERR_MODULE_NOT_FOUND
```

Uzantı yazmak bir külfet değil, çözümlemeyi tek adıma indiren bir sözleşmedir: bir
tanımlayıcı tek bir adrese karşılık gelir ve karşılık, dosya sistemine bakmadan
hesaplanabilir.

#### Paket Arama Zinciri

Paket tanımlayıcısı görüldüğünde, içe aktaran dosyanın bulunduğu dizinden başlanır ve
her adımda `node_modules` alt dizinine bakılır. Bulunamazsa bir üst dizine çıkılır;
kök dizine kadar sürer.

```js
// cozumleme/derin/daha-derin/derin.js
import { ad } from 'olcum-araclari';
console.log('ust dizindeki node_modules bulundu:', ad);
```

```sh
node cozumleme/derin/daha-derin/derin.js
```

```
ust dizindeki node_modules bulundu: olcum-araclari
```

Dosya iki dizin derindedir ve yanında bağımlılık dizini yoktur; ad, zincir yukarı
tırmanılarak bulunmuştur. Bu kural, Modüller, Araçlar ve Ekosistem kursunda anlatılan
bağımlılık ağacı yerleşiminin dayanağıdır: aynı paketin farklı sürümleri, farklı
derinliklerdeki dizinlere yerleştirilerek bir arada bulunabilir.

#### Dışa Aktarım Haritası

Bir paketin `exports` alanı, dışarıdan hangi yolların içe aktarılabileceğini belirler.
Yukarıdaki paket iki giriş tanımlıyor: kök (`.`) ve `./birim`. Üçüncü dosya haritada
yer almıyor:

```sh
(cd cozumleme && node -e "import('olcum-araclari/ic.js').catch(e => console.log(e.code))")
```

```
ERR_PACKAGE_PATH_NOT_EXPORTED
```

Bu, dosyanın var olmamasından farklı bir hatadır: dosya yerinde duruyor, ama paket
onu genel arayüzünün parçası saymıyor. Harita, paket yazarına Programlama Temelleri
kursunda tanıtılan arayüz–gerçekleştirim ayrımını uygulama olanağı verir. Harita
olmadığında paketin bütün dosyaları dışarıya açıktır ve içeride yapılan her yeniden
düzenleme kullanıcıları kırabilir.

Bir paket kendi içinde de takma ad tanımlayabilir. `imports` alanındaki, `#` ile
başlayan adlar yalnızca o paketin kendi dosyalarından çözülür; dışarıdan
görünmezler. Bu düzenek Paket Yayımlama dersinde yeniden ele alınacak.

#### Çözümlemeyi Sorgulamak

Hangi tanımlayıcının hangi dosyaya karşılık geldiği programdan sorulabilir:

```js
// cozumleme/coz.js
const yollar = ['node:fs', './ozet.js', 'olcum-araclari', 'olcum-araclari/birim'];
for (const yol of yollar) {
  const cozulen = import.meta.resolve(yol);
  console.log(yol.padEnd(22), cozulen.replace(/^.*\/cozumleme\//, './'));
}
```

```sh
node cozumleme/coz.js
```

```
node:fs                node:fs
./ozet.js              ./ozet.js
olcum-araclari         ./node_modules/olcum-araclari/index.js
olcum-araclari/birim   ./node_modules/olcum-araclari/birim.js
```

Çıktıdaki yollar dosya adresleridir; örnekte okunabilirlik için ortak önek
kırpılmıştır, gerçek değerler `file://` ile başlar. Yerleşik modül ise adresle değil,
kendi tanımlayıcısıyla döner — dosya sisteminde karşılığı olmadığı için.

Bu sorgulama, bir bağımlılığın beklenen dosyadan mı geldiğini doğrulamanın en kısa
yoludur. `import.meta.resolve` yalnızca ES modüllerinde bulunur; CommonJS tarafında
karşılığı `require.resolve` çağrısıdır.

İki biçim arasında köprü kurmak gerektiğinde, `node:module` modülündeki
`createRequire` fonksiyonu bir ES modülünün içinde çalışan bir `require` üretir. Bu, ES
modülü olarak yayımlanmamış bir bağımlılığı kullanmanın yoludur ve Paket Yayımlama
dersinde kullanılacaktır.

#### Modüller Bir Kez Değerlendirilir

Bir modül, çözümlenen adresi başına bir kez değerlendirilir; aynı adres birden çok
yerden içe aktarılsa da gövdesi yeniden çalışmaz ve aynı dışa aktarım nesnesi
paylaşılır. Bu, modül düzeyinde tutulan durumun süreç boyunca tek olduğu anlamına
gelir — bir sayaç, bir bağlantı havuzu veya bir yapılandırma nesnesi modül gövdesinde
oluşturulduğunda, onu içe aktaran herkes aynı örneği görür.

Ayrıntı şurada: teklik, tanımlayıcıya değil çözümlenen adrese bağlıdır. Aynı paketin
iki farklı dizinde bulunan iki kopyası iki ayrı adres verir ve iki ayrı örnek üretir.
Bağımlılık ağacında yinelenen sürümlerin ortaya çıkardığı sorunun kökeni budur.

`import` bildirimi yalnızca modülün en üst düzeyinde ve koşulsuz yazılabilir; çünkü
bağımlılıklar gövde çalışmadan önce çözülür. Koşula bağlı yükleme gerektiğinde, bir
söz döndüren `import()` fonksiyon biçimi kullanılır. Ölçüm toplayıcı bu biçimi, yalnızca
istendiğinde yüklenen çıktı biçimlendiricileri için kullanabilir.

#### Özet

- Tanımlayıcılar üç türdür: `node:` önekli yerleşikler, `./` veya `../` ile başlayan
  dosya yolları ve bunların dışındaki paket adları. Tür, çözümleme kuralını belirler.
- ES modüllerinde dosya uzantısı zorunludur; uzantı tahmini ve dizin dosyası araması
  CommonJS'e özgüdür.
- Paket adı, içe aktaran dosyanın dizininden başlayarak yukarı doğru her düzeydeki
  `node_modules` dizininde aranır.
- `exports` haritası paketin genel arayüzünü sabitler; haritada olmayan bir dosyaya
  erişim, dosya var olsa bile reddedilir.
- Modül, çözümlenen adres başına bir kez değerlendirilir; modül düzeyindeki durum o
  adresi paylaşan herkes için tektir.

#### Sonraki Adım

Çalışma zamanı modelinin dört parçası tamamlandı: barındıran ortam, olay döngüsü,
süreç nesnesi ve modül çözümleme. Bundan sonraki konu, bu model üzerine kurulmuş
yerleşik modülleri sırayla ele alır. İlk durak dosya sistemidir: aynı okuma işini
sunan üç ayrı arayüz vardır ve aralarındaki seçim, olay döngüsü dersinde ölçülen
engelleme bedelini doğrudan belirler.

## Yerleşik Modüller

Dosya sistemi ve yol arayüzleri, akışlar ve geri basınç, arabellekler, olay yayıcı, HTTP sunucusu, alt süreçler ve çok çekirdek kullanımı.

### Dosya Sistemi

Source: https://academia.sh/tr/kurslar/nodejs/dosya-sistemi

Aynı işi sunan üç arayüzün farkı, iş parçacığı havuzunun rolü, hata kodlarıyla dallanma, dizin işlemleri ve yarım yazılmış dosya bırakmayan güncelleme.

Çalışma zamanı modeli konusu, engelleyen işin olay döngüsünü nasıl durdurduğunu
ölçtü. Dosya sistemi arayüzü bu ölçünün doğrudan uygulama alanıdır: aynı okuma işi
için üç ayrı çağrı biçimi sunulur ve üçü olay döngüsüne farklı davranır.

Bu ders üç biçimi karşılaştırır, hangi durumda hangisinin doğru olduğunu belirler ve
ölçüm toplayıcının ilk gerçek adımını yazar: dosyayı okuyup özetini üretmek.

#### Üç Arayüz

Yerleşik dosya sistemi modülü aynı işlemleri üç ayrı biçimde sunar. Eşzamanlı biçim
`node:fs` modülünde `Sync` sonekiyle, geri çağrılı biçim aynı modülde soneksiz,
söz tabanlı biçim ise `node:fs/promises` modülünde bulunur.

Bu dersin örnekleri kursun çalışma dosyası olan `olcumler.ndjson` dosyasını okur.
Dosya çalışma dizininizde yoksa aşağıdaki komut onu oluşturur; içerik, çalışma
zamanı modeli konusunda verilen on iki satırın aynısıdır:

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

```js
// fs-uc-arayuz.mjs
import { readFileSync, readFile } from 'node:fs';
import { readFile as readFileSoz } from 'node:fs/promises';

const dosya = 'olcumler.ndjson';
const satirSay = (metin) => metin.trim().split('\n').length;

// 1. Eszamanli
console.log('eszamanli :', satirSay(readFileSync(dosya, 'utf8')));

// 2. Geri cagrili
readFile(dosya, 'utf8', (hata, metin) => {
  if (hata) throw hata;
  console.log('geri cagri:', satirSay(metin));
});

// 3. Soz tabanli
readFileSoz(dosya, 'utf8').then((metin) => {
  console.log('soz       :', satirSay(metin));
});

console.log('senkron akis burada devam etti');
```

```sh
node fs-uc-arayuz.mjs
```

```
eszamanli : 12
senkron akis burada devam etti
geri cagri: 12
soz       : 12
```

Çıktının sırası üç biçimin farkını gösterir. Eşzamanlı çağrı, sonucu döndürene kadar
sürecin ilerlemesini durdurdu; sonucu ikinci satırdan önce yazdı. Diğer ikisi işi
başlatıp geri döndü, bu yüzden `senkron akis burada devam etti` satırı onların
sonucundan önce göründü.

Geri çağrılı biçimin imzası bir sözleşme taşır: ilk parametre hatadır, sonuç ikinci
parametredir. Hata yoksa ilk parametre `null` gelir. Bu düzen, sözler dile girmeden
önce eşzamansız işin tek yoluydu ve yerleşik modüllerin çoğunda hâlâ bulunur.

Söz tabanlı biçim aynı işi `async`/`await` ile sıralı okunabilir hâle getirir. Kurs
boyunca varsayılan seçim budur.

Seçim kuralı şudur:

- **Eşzamanlı biçim**, olay döngüsü henüz iş görmüyorken kullanılır: başlangıçtaki
  yapılandırma okuması, tek seferlik bir betiğin gövdesi, kapanış anındaki son
  eşzamanlı yazma. Bir isteği işlerken kullanılmaz.
- **Söz tabanlı biçim**, hizmet kodunun varsayılanıdır.
- **Geri çağrılı biçim**, söz sarmalayıcısının maliyetinden kaçınmak gereken sıcak
  yollarda ve söz tabanlı karşılığı olmayan çağrılarda kullanılır.

#### Eşzamansızlık Nereden Geliyor

Ağ soketleri için beklemeyi çekirdeğin bildirim düzeneği üstlenir; süreç yoklama
aşamasında uyur ve veri geldiğinde uyanır. Dosya sisteminde durum farklıdır: yaygın
işletim sistemlerinde sıradan dosyalar için karşılaştırılabilir bir bildirim düzeneği
yoktur.

Çalışma zamanı bu boşluğu bir **iş parçacığı havuzu (thread pool)** ile doldurur.
Dosya okuma, yazma ve dizin listeleme çağrıları havuzdaki bir iş parçacığına verilir;
o iş parçacığı engelleyen sistem çağrısını yapar, iş bitince sonuç ana iş parçacığının
kuyruğuna bırakılır. Ana iş parçacığı bu sırada başka istekleri işlemeye devam eder.

İki sonucu vardır. Birincisi, dosya işlemleri gerçekten paralel yürür; ikincisi,
havuzun sabit bir genişliği vardır. Aynı anda havuz genişliğinden çok dosya işlemi
başlatılırsa fazlası sıraya girer. Havuz genişliği bir ortam değişkeniyle
ayarlanabilir; ölçmeden değiştirmek yarar sağlamaz.

#### Hata Kodlarıyla Dallanma

Dosya sistemi hataları, iletileriyle değil `code` alanlarıyla ayırt edilir. İleti
metni okunabilirlik içindir ve değişebilir; kod, işletim sisteminin sistem
çağrısından döndürdüğü ada karşılık gelir.

```js
// fs-hata.mjs
import { readFile } from 'node:fs/promises';

async function guvenliOku(yol) {
  try {
    return await readFile(yol, 'utf8');
  } catch (hata) {
    if (hata.code === 'ENOENT') return '';       // dosya yok: bos veri
    if (hata.code === 'EACCES') {                 // izin yok: cagirana birak
      throw new Error(`okuma izni yok: ${yol}`, { cause: hata });
    }
    throw hata;
  }
}

console.log('var olan  :', (await guvenliOku('olcumler.ndjson')).length, 'karakter');
console.log('olmayan   :', (await guvenliOku('yok.ndjson')).length, 'karakter');

try {
  await readFile('yok.ndjson', 'utf8');
} catch (hata) {
  console.log('kod:', hata.code, '| cagri:', hata.syscall, '| yol:', hata.path);
}
```

```sh
node fs-hata.mjs
```

```
var olan  : 1000 karakter
olmayan   : 0 karakter
kod: ENOENT | cagri: open | yol: yok.ndjson
```

`ENOENT` (dosya yok), `EACCES` (izin yok), `EISDIR` (dizin verildi), `ENOSPC`
(disk dolu) ve `EMFILE` (açık dosya sınırı aşıldı) en sık karşılaşılanlardır. Linux'a
Giriş kursundaki izin modeli, `EACCES` hatasının ne zaman geleceğini belirler:
sürecin kullanıcı kimliği ve grup üyeliği dosyanın izin bitleriyle karşılaştırılır.

Yakalanan her hatanın yutulmaması önemlidir. Yukarıdaki fonksiyon yalnızca bildiği iki
durumu ele alıyor, geri kalanı olduğu gibi yukarı fırlatıyor. Bilinmeyen bir kodu
sessizce boş veriye çevirmek, arızayı gizler.

#### Dizin İşlemleri

Dizin oluşturma, listeleme ve silme aynı modülde bulunur. Aşağıdaki blok örnek ağacı
kendi kurar, sonra listeler:

```js
// fs-dizin.mjs
import { mkdir, writeFile, readdir, stat, rm, readFile } from 'node:fs/promises';

// Örnek ağacı bu blok kendisi kurar
await rm('veri', { recursive: true, force: true });
await mkdir('veri/2024-02-07/ham', { recursive: true });   // ara dizinleri de acar
await writeFile('veri/2024-02-07/olcumler.ndjson', await readFile('olcumler.ndjson'));
await writeFile('veri/2024-02-07/not.txt', 'kenar-03 dugumu sabahtan beri sicak\n');

const girdiler = await readdir('veri/2024-02-07', { withFileTypes: true });
for (const girdi of girdiler.sort((a, b) => a.name.localeCompare(b.name))) {
  if (girdi.isDirectory()) {
    console.log(`dizin     - ${girdi.name}`);
  } else {
    const { size } = await stat(`veri/2024-02-07/${girdi.name}`);
    console.log(`dosya ${String(size).padStart(5)} ${girdi.name}`);
  }
}
```

```sh
node fs-dizin.mjs
```

```
dizin     - ham
dosya    36 not.txt
dosya  1000 olcumler.ndjson
```

`recursive` seçeneği iki yerde geçiyor ve iki farklı işi yapıyor. `mkdir` çağrısında
ara dizinleri açar ve dizin zaten varsa hata vermez; bu, işlemi yinelemeye dayanıklı
kılar. `rm` çağrısında ise alt ağacın tamamını siler — `force` ile birlikte, hedef
yoksa da hata vermez.

`rm` çağrısı geri alınamaz. Linux'a Giriş kursunda kabuk için söylenen kural burada
da geçerlidir: silinecek yolu bir değişkenden alıyorsanız, silmeden önce yolun
beklediğiniz kök altında kaldığını doğrulayın. Bu doğrulamanın nasıl yazıldığı
sonraki dersin konusudur.

`readdir` çağrısındaki `withFileTypes` seçeneği, adların yanında tür bilgisini de
getirir; onsuz her ad için ayrı bir `stat` çağrısı gerekirdi. Dönen girdilerin sırası
dosya sistemine bağlıdır ve alfabetik olduğu varsayılamaz — örnekteki sıralama
bunun içindir.

#### Yarım Dosya Bırakmadan Yazmak

Ölçüm toplayıcının ilk çıktısı, düğüm ve metrik başına özet üreten bir dosyadır.
Doğrudan hedef dosyanın üzerine yazmak bir risk taşır: yazma sırasında süreç
sonlanırsa dosya yarım kalır ve onu okuyan program bozuk veri görür.

Çözüm, geçici bir ada yazıp tamamlandığında yerine koymaktır. Aynı dosya sistemi
içinde ad değiştirme işlemi bölünmez: okuyucular ya eski dosyanın tamamını ya yeni
dosyanın tamamını görür.

```js
// ozetle.mjs
import { readFile, writeFile, rename, stat } from 'node:fs/promises';

async function ozetle(kaynak, hedef) {
  const metin = await readFile(kaynak, 'utf8');
  const kova = new Map();

  for (const satir of metin.split('\n')) {
    if (satir.trim() === '') continue;
    const { dugum, metrik, deger } = JSON.parse(satir);
    const anahtar = `${dugum}/${metrik}`;
    const kayit = kova.get(anahtar) ?? { sayi: 0, toplam: 0 };
    kayit.sayi += 1;
    kayit.toplam += deger;
    kova.set(anahtar, kayit);
  }

  const ozet = [...kova].map(([anahtar, { sayi, toplam }]) => ({
    anahtar,
    sayi,
    ortalama: Number((toplam / sayi).toFixed(2)),
  }));

  // Yarim yazilmis dosya birakmamak icin once gecici ada yaz, sonra yerine koy
  const gecici = `${hedef}.gecici`;
  await writeFile(gecici, JSON.stringify(ozet, null, 2) + '\n');
  await rename(gecici, hedef);

  const bilgi = await stat(hedef);
  return { kayitSayisi: ozet.length, bayt: bilgi.size };
}

const sonuc = await ozetle('olcumler.ndjson', 'ozet.json');
console.log(`${sonuc.kayitSayisi} ozet kaydi, ${sonuc.bayt} bayt yazildi`);
```

```sh
node ozetle.mjs
```

```
6 ozet kaydi, 477 bayt yazildi
```

Geçici dosya hedefle **aynı dizinde** oluşturulmalıdır. Ad değiştirme farklı dosya
sistemlerine yayılamaz: çekirdek işlemi kopyala–sil olarak yürütmez, `EXDEV` ile
başarısız kılar. Güvence bu yüzden sessizce zayıflamaz; işlem hiç gerçekleşmez.

Bu yaklaşımın sınırı da vardır: veri ancak süreç bittikten sonra değil, ad değiştirme
işleminden sonra görünür hâle gelir, ama diske gerçekten yazıldığı an işletim sistemi
önbelleğine bağlıdır. Elektrik kesintisine karşı dayanıklılık gerekiyorsa dosya
tanıtıcısı üzerinden eşitleme çağrısı yapılır; sıradan hizmetlerde bu ek maliyet
genellikle üstlenilmez.

Üretilen `ozet.json` dosyası kurs boyunca hizmetin sunacağı içeriktir; sonraki
derslerde aynı özet, önce akışlarla, sonra HTTP üzerinden üretilecek.

#### Özet

- Dosya sistemi işlemleri üç arayüzle sunulur: eşzamanlı, geri çağrılı ve söz
  tabanlı. Eşzamanlı biçim yalnızca olay döngüsü iş görmüyorken kullanılır.
- Dosya işlemlerinin eşzamansızlığı iş parçacığı havuzundan gelir; havuz sabit
  genişliktedir ve aşırı eşzamanlı istek sıraya girer.
- Hatalar `code` alanıyla ayırt edilir; ileti metnine dayanan dallanma kırılgandır.
- `mkdir` çağrısındaki özyineli seçenek işlemi yinelemeye dayanıklı kılar; `readdir`
  çağrısının döndürdüğü sıra dosya sistemine bağlıdır.
- Bir çıktı dosyası, geçici ada yazılıp aynı dizin içinde ad değiştirilerek
  güncellendiğinde okuyucular hiçbir zaman yarım içerik görmez.

#### Sonraki Adım

Bu dersteki bütün yollar çalışma dizinine göre yazıldı; program başka bir dizinden
çağrıldığında dosyayı bulamaz. Ayrıca ayırıcı karakteri elle yazmak, aracı tek bir
platforma bağlar. Sonraki ders yol kurmayı ele alır: parçaları birleştirmenin,
göreli yolu mutlak yapmanın ve bir kullanıcı girdisinin kök dizin dışına çıkmadığını
doğrulamanın kuralları.

### Yol İşlemleri

Source: https://academia.sh/tr/kurslar/nodejs/yol-islemleri

Yol parçalarını birleştirme ve çözme, platform kurallarının ayrı sunulması, betiğin kendi konumundan dayanak kurma ve kök dizin dışına çıkışı engelleme.

Önceki derste bütün dosya adları çıplak yazıldı: `olcumler.ndjson`, `veri/2024-02-07`.
Bu adlar çalışma dizinine göre çözülür, dolayısıyla aynı program başka bir dizinden
çağrıldığında dosyayı bulamaz. Ayrıca ayırıcı karakteri elle yazmak, aracı tek bir
platformun yol kurallarına bağlar.

Bu ders yolları veri olarak ele alan yerleşik modülü tanıtır ve iki soruyu yanıtlar:
program kendi dosyalarını nereye göre arayacak, dışarıdan gelen bir yol parçası nereye
kadar gitmesine izin verilecek?

#### Birleştirme ve Çözme

`node:path` modülünün iki temel fonksiyonu vardır ve aralarındaki fark sık karıştırılır.

`join`, verilen parçaları platformun ayırıcısıyla birbirine ekler ve sonucu
normalleştirir; sonuç göreli verilmişse göreli kalır. `resolve` ise sağdan sola
yürüyerek mutlak bir yol üretir; hiçbir parça mutlak değilse çalışma dizinini başa
ekler.

```js
// yol.mjs
import path from 'node:path';

console.log('join      :', path.join('veri', '2024-02-07', 'olcumler.ndjson'));
console.log('join + .. :', path.join('veri', '2024-02-07', '..', 'ozet.json'));
console.log('sep       :', JSON.stringify(path.sep));
console.log();

const yol = 'veri/2024-02-07/olcumler.ndjson';
console.log('dirname   :', path.dirname(yol));
console.log('basename  :', path.basename(yol));
console.log('uzantisiz :', path.basename(yol, path.extname(yol)));
console.log('extname   :', path.extname(yol));
console.log('parse     :', path.parse(yol));
```

```sh
node yol.mjs
```

```
join      : veri/2024-02-07/olcumler.ndjson
join + .. : veri/ozet.json
sep       : "/"

dirname   : veri/2024-02-07
basename  : olcumler.ndjson
uzantisiz : olcumler
extname   : .ndjson
parse     : {
  root: '',
  dir: 'veri/2024-02-07',
  base: 'olcumler.ndjson',
  ext: '.ndjson',
  name: 'olcumler'
}
```

İkinci satır normalleştirmeyi gösteriyor: `..` parçası bir üst dizine çıkma anlamına
gelir ve sonuçta kaybolur. `sep` değeri çalıştığınız platforma göre değişir; örnekteki
eğik çizgi bir Unix türevinde alınmıştır.

Bu fonksiyonların hiçbiri dosya sistemine bakmaz. `extname` bir dosyanın gerçekten JSON
olduğunu söylemez, adının nasıl bittiğini söyler; `dirname` o dizinin var olduğunu
söylemez. Yol işlemleri saf dizgi işlemleridir — Programlama Temelleri kursundaki
saf fonksiyon tanımına uyarlar ve bu yüzden sınanmaları kolaydır.

#### Platform Kuralları Ayrı Sunulur

Modül, çalıştığı platformun kurallarını varsayılan olarak uygular, ama iki kural
kümesini de ayrı ayrı erişilebilir tutar.

```js
// yol-platform.mjs
import path from 'node:path';

console.log('bu platform :', path.join('veri', '2024-02-07', 'ozet.json'));
console.log('posix       :', path.posix.join('veri', '2024-02-07', 'ozet.json'));
console.log('win32       :', path.win32.join('veri', '2024-02-07', 'ozet.json'));
console.log();
console.log('win32 kurallariyla "C:\\veri" mutlak mi:', path.win32.isAbsolute('C:\\veri'));
console.log('posix kurallariyla "C:\\veri" mutlak mi:', path.posix.isAbsolute('C:\\veri'));
console.log('posix kurallariyla "/veri"   mutlak mi:', path.posix.isAbsolute('/veri'));
```

```sh
node yol-platform.mjs
```

```
bu platform : veri/2024-02-07/ozet.json
posix       : veri/2024-02-07/ozet.json
win32       : veri\2024-02-07\ozet.json

win32 kurallariyla "C:\veri" mutlak mi: true
posix kurallariyla "C:\veri" mutlak mi: false
posix kurallariyla "/veri"   mutlak mi: true
```

İlk satır, örneğin çalıştırıldığı platformun kurallarını yansıtır; bir Windows
makinesinde ters eğik çizgili biçim yazardı. Ayrı kural kümelerinin varlığı, yerel
dosya yollarıyla ilgisi olmayan işler için önemlidir: bir arşiv girdisinin veya bir
HTTP yolunun ayrıştırılması her platformda aynı sonucu vermelidir; bunun için
`path.posix` doğrudan seçilir.

Adres yolları için ayrıca `URL` nesnesi vardır ve iki alan karıştırılmamalıdır. Bir
istek yolundaki karakter kaçışları `path` fonksiyonlarının bilmediği kurallara uyar;
sunucu tarafında yol ayrıştırması adres nesnesiyle yapılır.

#### Bir Yoldan Başka Bir Yol Türetmek

Ölçüm toplayıcının sık yaptığı iş, bir girdi dosyasının adından çıktı dosyasının adını
üretmektir: `olcumler.ndjson` okunur, yanına `olcumler-ozet.json` yazılır. Dizgi
birleştirmesiyle yapıldığında bu iş uzantının kaç karakter olduğu varsayımına dayanır
ve `.ndjson` ile `.json` arasında bozulur.

`parse` bir yolu alanlarına ayırır, `format` alanlardan yol kurar. İkisi birlikte
kullanıldığında türetme, karakter saymadan yapılır — ancak `format` çağrısının bir
önceliği vardır:

```js
// yol-turetme.mjs
import path from 'node:path';

const kaynak = 'veri/2024-02-07/olcumler.ndjson';
const parcalar = path.parse(kaynak);

// base alani varsa ext ve name yok sayilir; turetmek icin base silinir
const hedef = path.format({ dir: parcalar.dir, name: `${parcalar.name}-ozet`, ext: '.json' });
const yanlis = path.format({ ...parcalar, name: `${parcalar.name}-ozet`, ext: '.json' });

console.log('kaynak     :', kaynak);
console.log('hedef      :', hedef);
console.log('base kalinca:', yanlis);
console.log();

console.log('relative (komsu)  :', path.relative('veri/2024-02-07', 'veri/2024-02-08/olcumler.ndjson'));
console.log('relative (ayni)   :', JSON.stringify(path.relative('veri', 'veri')));
console.log('relative (disari) :', path.relative('veri', 'gizli.txt'));
```

```sh
node yol-turetme.mjs
```

```
kaynak     : veri/2024-02-07/olcumler.ndjson
hedef      : veri/2024-02-07/olcumler-ozet.json
base kalinca: veri/2024-02-07/olcumler.ndjson

relative (komsu)  : ../2024-02-08/olcumler.ndjson
relative (ayni)   : ""
relative (disari) : ../gizli.txt
```

Üçüncü satır tuzağı gösteriyor. `format`, verilen nesnede `base` alanı varsa `name` ve
`ext` alanlarını yok sayar; `parse` çıktısı `base` alanını taşıdığı için doğrudan
yayılan bir nesne hiç değişmemiş yolu geri verir. Türetme yapılırken `base` ya
verilmez ya da yenisi hesaplanır.

Çıktının ikinci öbeği `relative` fonksiyonunu tanıtıyor: iki yol arasında, birinciden
ikinciye gitmek için izlenecek göreli adımı verir. Üç davranış ayrı ayrı anlamlıdır.
Komşu bir dizine giden yol bir üst düzeye çıkıp iner. İki yol aynıysa sonuç boş
dizgidir — bir alt öğe değil, yolun kendisi kastedilmiştir. Hedef, tabanın dışında
kalıyorsa sonuç `..` ile başlar.

Son davranış, bir sonraki bölümdeki kök denetiminin ikinci yazım biçimidir:
`path.relative(kok, tam)` sonucu `..` ile başlamıyorsa hedef kökün altındadır. Bu
biçim ayırıcı eklemeyi gerektirmez, ancak yol ayrımı büyük–küçük harf duyarsız bir
dosya sisteminde her iki biçim de aynı sınırla karşılaşır: dizgi karşılaştırması,
dosya sisteminin ad eşleme kuralını bilmez.

#### Betiğin Kendi Konumu

Bir programın kendi yanındaki dosyaları bulması, çalışma dizinine bağlı kalmamalıdır.
ES modüllerinde dayanak `import.meta.url` değeridir; bu bir dosya adresidir ve dosya
sistemi yoluna çevrilmesi gerekir.

```js
// yol-guvenlik.mjs
import path from 'node:path';
import { fileURLToPath } from 'node:url';

// Betiğin kendi konumu: çalışma dizininden bağımsız sabit dayanak
const buDosya = fileURLToPath(import.meta.url);
const buDizin = path.dirname(buDosya);
console.log('betigin dizini (son parca):', path.basename(buDizin));

const kok = path.resolve(buDizin, 'veri');

function guvenliCoz(kullaniciYolu) {
  const tam = path.resolve(kok, kullaniciYolu);
  if (tam !== kok && !tam.startsWith(kok + path.sep)) {
    throw new Error(`kok dizin disi: ${kullaniciYolu}`);
  }
  return path.relative(kok, tam);
}

for (const istek of ['2024-02-07/olcumler.ndjson', './a/../b.json', '../gizli.txt']) {
  try {
    console.log(`${istek.padEnd(28)} -> ${guvenliCoz(istek)}`);
  } catch (hata) {
    console.log(`${istek.padEnd(28)} -> RED: ${hata.message}`);
  }
}
```

Aynı betik iki farklı dizinden çağrıldığında aynı sonucu verir:

```sh
node yol-guvenlik.mjs
```

{/* mask: (?<=betigin dizini \(son parca\): )[^ ]+ */}
```
betigin dizini (son parca): lab
2024-02-07/olcumler.ndjson   -> 2024-02-07/olcumler.ndjson
./a/../b.json                -> b.json
../gizli.txt                 -> RED: kok dizin disi: ../gizli.txt
```

İlk satırdaki dizin adı, betiği hangi dizine kaydettiğinize göre değişir; örnekte
`lab` adlı bir dizinden alınmıştır. Önemli olan, komutu kök dizinden veya başka bir
yerden çağırdığınızda bu değerin değişmemesidir.

Adresten yola çevirme işi elle yapılamaz. Dosya adresinde boşluk `%20` ile kodlanır
ve Windows'ta sürücü harfi ek bir kurala uyar; `fileURLToPath` bu ayrıntıları
üstlenir. Ters yönde, bir yolu adrese çevirmek için aynı modüldeki `pathToFileURL`
kullanılır.

#### Kök Dizin Dışına Çıkmayı Engellemek

Yukarıdaki `guvenliCoz` fonksiyonu, ölçüm toplayıcının ileride karşılaşacağı bir sorunu
şimdiden çözer. Hizmet, istek yolundan gelen bir ad ile dosya açacaksa, o adın
belirlenen kök dizinin altında kaldığı doğrulanmalıdır. Doğrulanmazsa `../` dizisi
içeren bir istek, kökün dışındaki dosyalara erişebilir.

Doğrulamanın üç adımı vardır ve sırası önemlidir:

1. Kök dizini **mutlak** yola çevir.
2. Kullanıcı yolunu kökün altına çöz. `resolve` bu adımda `..` parçalarını uygular.
3. Sonucun kök dizine eşit olduğunu veya kök artı ayırıcı ile başladığını doğrula.

Üçüncü adımdaki ayırıcı ayrıntısı gereksiz görünür ama değildir. Yalnızca
`tam.startsWith(kok)` denetlenirse, `veri` kökü için `veri-yedek` adlı komşu dizin
de denetimi geçer; ayırıcının eklenmesi bu sızıntıyı kapatır.

Bu denetim, dizgi düzeyinde bir denetimdir ve sembolik bağları hesaba katmaz. Kök
dizinin içinde dışarıyı gösteren bir sembolik bağ varsa, çözülen yol kökün altında
görünürken açılan dosya dışarıda olabilir. Linux'a Giriş kursunda tanıtılan sembolik
bağ davranışı burada devreye girer; gerçek yolu almak için dosya sistemine soran
`realpath` çağrısı kullanılır ve denetim onun sonucu üzerinde yinelenir.

#### Özet

- `join` parçaları birleştirip normalleştirir ve göreliliği korur; `resolve` sağdan
  sola yürüyerek mutlak yol üretir.
- Yol fonksiyonları dosya sistemine bakmaz; adın biçimi hakkında konuşurlar, dosyanın
  varlığı hakkında değil.
- Modül, iki platformun kurallarını ayrı ayrı sunar; yerel dosya dışındaki yollar
  için kural kümesi açıkça seçilir.
- `parse` ve `format` ile bir yoldan başkası karakter saymadan türetilir; `format`,
  nesnede `base` alanı varsa `name` ve `ext` alanlarını yok sayar.
- ES modülünde betiğin kendi konumu `import.meta.url` adresinden `fileURLToPath` ile
  elde edilir; bu dayanak çalışma dizininden bağımsızdır.
- Dışarıdan gelen yol, mutlak köke çözülüp kök artı ayırıcı önekiyle sınanarak
  denetlenir; sembolik bağlar için gerçek yol ayrıca sorulur.

#### Sonraki Adım

Buraya kadar dosyalar bir kerede belleğe alındı. On iki satırlık bir dosyada bu
sorunsuzdur; günlerce biriken bir ölçüm dosyasında değildir. Sonraki ders veriyi
parça parça taşıyan akış soyutlamasını kurar: okunabilir, yazılabilir ve dönüştürücü
akışları, bunları birbirine bağlamanın doğru yolunu ve üreticinin tüketiciden hızlı
olduğunda devreye giren geri basıncı ele alır.

### Akışlar

Source: https://academia.sh/tr/kurslar/nodejs/akislar

Dört akış türü, parça sınırlarının kayıt sınırlarıyla örtüşmemesi, nesne kipi, boru hattı kurma ve geri basıncın ölçülmesi.

Önceki iki ders dosyayı bir kerede belleğe aldı. On iki satırlık bir dosyada bunun
bedeli yoktur. Ölçüm düğümleri günlerce yazdığında dosya yüzlerce megabayta ulaşır ve
aynı kod, dosyanın tamamı kadar bellek ister — sonra o belleği bir kez daha kopyalayarak
satırlara böler.

Bu dersin sorusu şudur: veriyi tamamını görmeden nasıl işleriz? Yanıt, Kabuk
Programlama kursundaki boru hattı modelinin aynısıdır — veri bir uçtan girer, birkaç
dönüşümden geçer, öbür uçtan çıkar; hiçbir adım tamamını tutmaz. Çalışma zamanı bu
modeli birinci sınıf nesnelerle sunar.

#### Dört Tür

**Akış (stream)**, veriyi parça parça taşıyan bir soyutlamadır. Dört türü vardır:

- **Okunabilir akış**, veri üretir. Dosya okuma, gelen HTTP isteği, alt sürecin
  standart çıktısı bu türdendir.
- **Yazılabilir akış**, veri tüketir. Dosya yazma, giden HTTP yanıtı, standart çıktı
  bu türdendir.
- **Çift yönlü akış**, ikisi birden olan bağımsız iki yön taşır; ağ soketi böyledir.
- **Dönüştürücü akış**, çift yönlünün özel hâlidir: yazılan veriyi işleyip okunabilir
  tarafına çıkarır. Sıkıştırma, şifreleme ve ayrıştırma bu türdendir.

Bütün akışlar olay yayıcıdır: `data`, `end`, `error`, `drain` gibi olaylar yayarlar.
Olay yayıcının kendisi sonraki derslerden birinin konusudur; burada olayları doğrudan
dinlemek yerine daha üst düzey araçlar kullanılacak.

#### Parça Sınırı Kayıt Sınırı Değildir

Okunabilir akış, veriyi kendi belirlediği büyüklükte parçalar hâlinde verir. Bu
büyüklük `highWaterMark` seçeneğiyle ayarlanır ve dosyadaki satır sınırlarıyla hiçbir
ilgisi yoktur. Aşağıdaki örnekler yine kursun çalışma dosyasını okur; dosya
çalışma dizininizde yoksa önce şu komutla oluşturun:

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

```js
// akis-parca.mjs
import { createReadStream } from 'node:fs';

const akis = createReadStream('olcumler.ndjson', { highWaterMark: 256 });

let sira = 0;
for await (const parca of akis) {
  sira += 1;
  console.log(`parca ${sira}: ${parca.length} bayt, tur ${parca.constructor.name}`);
}
console.log(`toplam ${sira} parca`);
```

```sh
node akis-parca.mjs
```

```
parca 1: 256 bayt, tur Buffer
parca 2: 256 bayt, tur Buffer
parca 3: 256 bayt, tur Buffer
parca 4: 232 bayt, tur Buffer
toplam 4 parca
```

Dosya 1000 bayttır; dört parçaya bölündü ve son parça kalanı taşıdı. Parçaların hiçbiri
bir satır sınırında bitmiyor. Bu, akışla çalışmanın temel zorluğudur: **kayıt sınırını
kurmak akışın değil, sizin işinizdir.**

Örnekteki `for await` döngüsü, okunabilir akışın eşzamansız yinelenebilir olmasından
yararlanır. Döngü gövdesi çalışırken akış duraklar; bu, geri basıncın en yalın
uygulamasıdır.

#### Nesne Kipi ve Dönüştürücü

Varsayılan olarak akışlar bayt taşır. **Nesne kipi (object mode)** açıldığında parça
yerine keyfi bir değer taşınır; böylece boru hattının bir yerinden sonra baytlar
değil kayıtlar akar.

Aşağıdaki dönüştürücü, bayt parçalarını satırlara böler ve yarım kalan satırı bir
sonraki parçayla birleştirmek üzere kendinde tutar. Bu, akış yazımının klasik
kalıbıdır.

```js
// akis-ozet.mjs
import { createReadStream } from 'node:fs';
import { Transform, Writable } from 'node:stream';
import { pipeline } from 'node:stream/promises';

// Bayt parcalarini satirlara boler; yarim kalan satiri kendinde tutar
class SatirBolucu extends Transform {
  #kalan = '';

  constructor() {
    super({ readableObjectMode: true });
  }

  _transform(parca, kodlama, bitti) {
    const metin = this.#kalan + parca.toString('utf8');
    const satirlar = metin.split('\n');
    this.#kalan = satirlar.pop();          // son parca yarim olabilir
    for (const satir of satirlar) {
      if (satir !== '') this.push(satir);
    }
    bitti();
  }

  _flush(bitti) {
    if (this.#kalan !== '') this.push(this.#kalan);
    bitti();
  }
}

// Satirlari kayda cevirip kova kova toplar
class OzetToplayici extends Writable {
  kovalar = new Map();

  constructor() {
    super({ objectMode: true });
  }

  _write(satir, kodlama, bitti) {
    let kayit;
    try {
      kayit = JSON.parse(satir);
    } catch (hata) {
      return bitti(new Error(`bozuk satir: ${satir.slice(0, 30)}`, { cause: hata }));
    }
    const anahtar = `${kayit.dugum}/${kayit.metrik}`;
    const kova = this.kovalar.get(anahtar) ?? { sayi: 0, toplam: 0 };
    kova.sayi += 1;
    kova.toplam += kayit.deger;
    this.kovalar.set(anahtar, kova);
    bitti();
  }
}

const toplayici = new OzetToplayici();
await pipeline(
  createReadStream('olcumler.ndjson', { highWaterMark: 256 }),
  new SatirBolucu(),
  toplayici,
);

for (const [anahtar, { sayi, toplam }] of toplayici.kovalar) {
  console.log(`${anahtar.padEnd(18)} ${String(sayi).padStart(2)} olcum, ortalama ${(toplam / sayi).toFixed(2)}`);
}
```

```sh
node akis-ozet.mjs
```

```
kenar-01/sicaklik   3 olcum, ortalama 21.87
kenar-01/nem        2 olcum, ortalama 47.60
kenar-02/sicaklik   3 olcum, ortalama 20.23
kenar-02/nem        1 olcum, ortalama 52.50
kenar-03/sicaklik   2 olcum, ortalama 24.35
kenar-03/nem        1 olcum, ortalama 41.30
```

Üç ayrıntı dikkat ister.

`readableObjectMode` yalnızca okunabilir tarafı nesne kipine alır; yazılabilir taraf
bayt kabul etmeye devam eder. Dönüştürücünün iki tarafı ayrı yapılandırılabilir ve
tam olarak bu örnek için gereklidir.

`_flush`, girdi bittiğinde bir kez çağrılır ve elde kalan yarım satırı çıkarma
fırsatı verir. Bu kanca olmadan, son satırı satır sonu ile bitmeyen dosyaların son
kaydı kaybolur.

`bitti` geri çağrısına bir hata verildiğinde, hata akışa yayılır ve boru hattını
düşürür. Yazılabilir akış içinde `throw` kullanmak aynı sonucu vermez.

#### Boru Hattı Kurmak

Akışları birbirine bağlamanın iki yolu vardır. `pipe` yöntemi eskidir ve bir kusuru
vardır: zincirin ortasındaki bir akış hata verdiğinde diğerleri kendiliğinden
kapanmaz, açık dosya tanıtıcıları ve bellekte tutulan tamponlar geride kalır.

`pipeline` bu kusuru kapatır. Zincirdeki herhangi bir akış hata verdiğinde tümünü yok
eder ve hatayı tek bir yerden bildirir.

Bozuk bir satır içeren dosyayla sınayalım. Önce dosyayı oluşturun:

```sh
printf '{"dugum":"kenar-01","metrik":"nem","deger":48.0}\nbozuk satir\n{"dugum":"kenar-02","metrik":"nem","deger":50.0}\n' > bozuk.ndjson
```

```js
// akis-hata.mjs
import { createReadStream } from 'node:fs';
import { Transform, Writable } from 'node:stream';
import { pipeline } from 'node:stream/promises';

const ayristir = new Transform({
  readableObjectMode: true,
  transform(parca, kodlama, bitti) {
    for (const satir of parca.toString('utf8').split('\n')) {
      if (satir === '') continue;
      try {
        this.push(JSON.parse(satir));
      } catch (hata) {
        return bitti(new Error(`ayristirilamayan satir: ${satir}`, { cause: hata }));
      }
    }
    bitti();
  },
});

let sayac = 0;
const say = new Writable({
  objectMode: true,
  write(kayit, kodlama, bitti) { sayac += 1; bitti(); },
});

const kaynak = createReadStream('bozuk.ndjson');

try {
  await pipeline(kaynak, ayristir, say);
  console.log('tamamlandi, kayit sayisi:', sayac);
} catch (hata) {
  console.log('boru hatti hatasi :', hata.message);
  console.log('asil neden        :', hata.cause.name);
  console.log('kaynak yok edildi :', kaynak.destroyed);
  console.log('hedef yok edildi  :', say.destroyed);
}
```

```sh
node akis-hata.mjs
```

```
boru hatti hatasi : ayristirilamayan satir: bozuk satir
asil neden        : SyntaxError
kaynak yok edildi : true
hedef yok edildi  : true
```

Son iki satır önemlidir: hata bildirildiğinde kaynak ve hedef zaten kapatılmıştı.
Dosya tanıtıcısı sızmadı. `node:stream/promises` modülündeki söz döndüren biçim,
bu temizliği `try`/`catch` ile birleştirir.

#### Geri Basınç

Veri Yapıları kursunda kuyruklar ele alınırken **geri basınç (backpressure)**
tanımlanmıştı: üretici tüketiciden hızlıysa, aradaki tamponun büyümesini durdurmanın
tek yolu üreticiyi yavaşlatmaktır. Aynı sorun Kabuk Programlama kursunda boru hattı
anlatılırken de geçti — orada yavaşlatmayı çekirdek yapıyordu.

Akışlarda bu iş yazma çağrısının dönüş değerine emanet edilir. `write` çağrısı,
tampondaki veri eşiği aştıysa `false` döner. Bu bir hata değildir, "yavaşla"
demektir; üretici `drain` olayını bekleyip devam eder.

Kurs boyunca iki sözcük ayrı tutulacak. **Tampon**, bir akışın içinde işlenmeyi
bekleyen veriyi tutan alandır; büyüklüğü `writableLength` alanından okunur.
**Arabellek** ise ham baytları temsil eden nesnedir ve sonraki dersin konusudur.
İngilizce her ikisi için de *buffer* sözcüğünü kullanır; ayrım Türkçe karşılıklarla
korunuyor.

```js
// geri-basinc.mjs
import { Writable } from 'node:stream';
import { once } from 'node:events';

// Her kaydi 5 ms'de isleyen, en fazla 4 kayit tamponlayan hedef
class YavasYazici extends Writable {
  constructor() {
    super({ objectMode: true, highWaterMark: 4 });
  }
  _write(kayit, kodlama, bitti) {
    setTimeout(bitti, 5);
  }
}

function kayitlar(adet) {
  return Array.from({ length: adet }, (_, i) => ({ sira: i }));
}

// 1. Donus degerini yok sayan uretici
const a = new YavasYazici();
for (const kayit of kayitlar(50)) a.write(kayit);
console.log('yok sayan  -> tamponda bekleyen kayit:', a.writableLength);
a.end();

// 2. Donus degerini dinleyen uretici
const b = new YavasYazici();
let enYuksek = 0;
for (const kayit of kayitlar(50)) {
  if (!b.write(kayit)) {
    enYuksek = Math.max(enYuksek, b.writableLength);
    await once(b, 'drain');
  }
}
console.log('dinleyen   -> tamponda gorulen en yuksek deger:', enYuksek);
b.end();
```

```sh
node geri-basinc.mjs
```

```
yok sayan  -> tamponda bekleyen kayit: 50
dinleyen   -> tamponda gorulen en yuksek deger: 4
```

İki sayı arasındaki fark, geri basıncın tamamıdır. Dönüş değerini yok sayan üretici
elli kaydın tamamını belleğe yığdı; eşik 4 olmasına rağmen tampon 50'ye çıktı. Dönüş
değerini dinleyen üretici tamponu hiç 4'ün üstüne çıkarmadı.

Bu farkın üretimdeki karşılığı bellek tüketimidir. Yavaş bir istemciye hızlı üretilen
veriyi yazan bir sunucu, dönüş değerini yok sayarsa istemci başına sınırsız bellek
biriktirir; birkaç yavaş istemci süreci bellek sınırına dayar.

İyi haber şudur: `pipeline` ve `pipe` bu denetimi kendiliğinden yapar; `for await`
döngüsü de öyle. Dönüş değerini elle yönetmek yalnızca akışları doğrudan sürdüğünüz
yerlerde gerekir.

Satır tabanlı okuma için ayrıca yerleşik bir kolaylık vardır: `node:readline`
modülünün arayüzü, bir okunabilir akışı satır satır yinelenebilir hâle getirir ve
yukarıdaki bölme mantığını üstlenir. Kendi dönüştürücünüzü yazmak, kaydın satırdan
başka bir şeyle sınırlandığı biçimlerde gerekir.

#### Özet

- Akışlar veriyi parça parça taşır; dört türü okunabilir, yazılabilir, çift yönlü ve
  dönüştürücüdür.
- Parça sınırları kayıt sınırlarıyla örtüşmez; kaydı kurmak, yarım kalan veriyi
  saklayan bir dönüştürücünün işidir ve `_flush` kancası son kaydı kurtarır.
- Nesne kipi, boru hattının bir noktasından sonra bayt yerine kayıt akmasını sağlar;
  dönüştürücünün iki tarafı ayrı yapılandırılabilir.
- `pipeline`, zincirdeki bir hata durumunda bütün akışları yok eder ve hatayı tek
  yerden bildirir; `pipe` bu temizliği yapmaz.
- `write` çağrısının `false` dönmesi geri basınç bildirimidir; yok sayıldığında
  tampon sınırsız büyür. `pipeline`, `pipe` ve `for await` bu denetimi üstlenir.

#### Sonraki Adım

Bu derste parçaların türü `Buffer` olarak göründü ve metne çevrilirken kodlama adı
elle verildi. Parçanın bir karakterin ortasından bölünmesi durumunda ne olduğu ise
sorulmadı. Sonraki ders ham baytların temsilini ele alır: arabellek nesnesinin ne
olduğunu, çok baytlı karakterlerin parça sınırında nasıl bozulduğunu ve ikili bir
kayıt biçiminin nasıl kodlanıp çözüldüğünü inceler.

### Arabellekler

Source: https://academia.sh/tr/kurslar/nodejs/arabellekler

Ham bayt dizisinin dizgiden farkı, sıfırlanmış ve sıfırlanmamış ayırma, görünüm ile kopya ayrımı, karakter sınırında bölünme sorunu ve ikili kayıt kodlama.

Önceki derste akıştan gelen parçaların türü `Buffer` göründü ve metne çevrilirken
kodlama adı elle verildi. Bir parçanın çok baytlı bir karakterin ortasından bölünmesi
durumunda ne olacağı ise sorulmadan bırakıldı.

Bu ders o soruyu yanıtlar. Konu, Bilgisayarlar Nasıl Çalışır kursundaki karakter
kodlamaları ve bayt sırası derslerinin doğrudan sürdürülmesidir: orada kurulan
kavramlar burada çalışan koda dönüşür.

#### Bayt Dizisi Olarak Arabellek

**Arabellek (buffer)**, sabit uzunlukta ham bayt dizisini temsil eden nesnedir.
Dilin `Uint8Array` tipinden türer; dolayısıyla dizin erişimi, uzunluk ve yineleme
davranışı tipli dizilerle aynıdır. Üzerine eklenen şey, kodlama dönüşümleri ve
sayısal alan okuma-yazma yöntemleridir.

Dizgi ile arabellek arasındaki fark, ölçü biriminde görülür:

```js
// arabellek.mjs
const metin = 'sıcaklık';

const ab = Buffer.from(metin, 'utf8');
console.log('karakter sayisi :', metin.length);
console.log('bayt sayisi     :', ab.length, '=', Buffer.byteLength(metin, 'utf8'));
console.log('onaltilik       :', ab.toString('hex'));
console.log('Uint8Array mi   :', ab instanceof Uint8Array);
console.log('ilk uc bayt     :', [...ab.subarray(0, 3)]);
```

```sh
node arabellek.mjs
```

```
karakter sayisi : 8
bayt sayisi     : 10 = 10
onaltilik       : 73c4b163616b6cc4b16b
Uint8Array mi   : true
ilk uc bayt     : [ 115, 196, 177 ]
```

Sekiz karakterlik dizgi on bayt tuttu; `ı` harfi UTF-8'de iki bayt ile
(`c4 b1`) kodlanır ve sözcükte iki kez geçer. Bu ayrım, bir HTTP yanıtına
`Content-Length` başlığı yazarken önemlidir: başlığa yazılması gereken sayı bayt
sayısıdır, karakter sayısı değil. `Buffer.byteLength`, arabellek üretmeden bu sayıyı
verir.

#### Ayırma, Görünüm ve Kopya

Arabellek üretmenin üç yolu vardır ve aralarındaki fark bellek davranışındadır.

```js
// arabellek-ayirma.mjs
const sifirli = Buffer.alloc(8);
console.log('alloc(8)          :', sifirli.toString('hex'));
console.log('allocUnsafe(8) boy:', Buffer.allocUnsafe(8).length);

// subarray bir kopya degil, ayni bellegin uzerine acilmis bir gorunumdur
const tam = Buffer.from('kenar-01', 'utf8');
const gorunum = tam.subarray(0, 5);
gorunum[0] = 'K'.charCodeAt(0);
console.log('gorunum degistirildi ->', tam.toString('utf8'));

// Kopya gerektiginde acikca istenir
const kopya = Buffer.from(tam.subarray(0, 5));
kopya[0] = 'x'.charCodeAt(0);
console.log('kopya degistirildi   ->', tam.toString('utf8'), '|', kopya.toString('utf8'));

// Parcalari tek arabellekte birlestirmek
const birlesik = Buffer.concat([Buffer.from('kenar'), Buffer.from('-'), Buffer.from('01')]);
console.log('concat               ->', birlesik.toString('utf8'), `(${birlesik.length} bayt)`);
```

```sh
node arabellek-ayirma.mjs
```

```
alloc(8)          : 0000000000000000
allocUnsafe(8) boy: 8
gorunum degistirildi -> Kenar-01
kopya degistirildi   -> Kenar-01 | xenar
concat               -> kenar-01 (8 bayt)
```

`Buffer.alloc`, istenen boyutta sıfırlanmış bellek verir. `Buffer.allocUnsafe`
sıfırlama adımını atlar ve daha hızlıdır; karşılığında arabellek, o bellekte daha
önce ne varsa onu içerir. İçeriği baştan sona yazmadan bu arabelleği kullanmak,
sürecin başka bir yerinde bulunan veriyi sızdırabilir. Kural: **arabelleği hemen ve
tamamen doldurmuyorsanız sıfırlanmış ayırma kullanın.**

Çıktının üçüncü satırı görünüm davranışını gösteriyor: `subarray` yeni bellek
ayırmaz, aynı belleğe açılmış bir pencere döndürür. Pencereye yazmak asıl arabelleği
değiştirir. Bu, kopyalamadan dilimleme olanağı verdiği için akış kodunda değerlidir;
farkında olunmadığında ise açıklaması zor hatalar üretir. Bağımsız bir kopya
gerektiğinde `Buffer.from` ile açıkça istenir.

`Buffer.concat`, parça listesini tek bir arabellekte birleştirir. Akıştan gelen
parçaları toplayıp sonunda tek metne çevirmenin standart yolu budur; parçaları
dizgiye çevirip birleştirmek, aşağıdaki soruna yol açar.

#### Karakter Sınırında Bölünme

Bir parça, çok baytlı bir karakterin ortasında bitebilir. O parça tek başına metne
çevrilirse, yarım kalan karakter yerine değiştirme karakteri konur ve bilgi geri
dönüşsüz kaybolur.

```js
// arabellek-sinir.mjs
import { StringDecoder } from 'node:string_decoder';

const ab = Buffer.from('sıcaklık', 'utf8');   // 10 bayt
const parcaA = ab.subarray(0, 2);              // 'ı' harfinin ortasindan keser
const parcaB = ab.subarray(2);

console.log('dogrudan cozme :', parcaA.toString('utf8') + parcaB.toString('utf8'));

const cozucu = new StringDecoder('utf8');
console.log('cozucu ile     :', cozucu.write(parcaA) + cozucu.write(parcaB) + cozucu.end());
```

```sh
node arabellek-sinir.mjs
```

```
dogrudan cozme : s��caklık
cozucu ile     : sıcaklık
```

İlk satırdaki iki değiştirme karakteri, yarım kalan `ı` harfinin iki baytının ayrı
ayrı çözülmesinden doğdu. `node:string_decoder` modülündeki çözücü, tamamlanmamış
bayt dizisini kendinde tutar ve sonraki yazımla birleştirir; `end` çağrısı, elde
kalan eksik dizi varsa onu bildirir.

Bu, önceki dersteki `SatirBolucu` sınıfının gizli varsayımını açığa çıkarır. Orada
her parça doğrudan `toString('utf8')` ile çevrildi; ölçüm dosyası yalnızca ASCII
karakterler içerdiği için sorun görünmedi. Düğüm adlarında Türkçe karakter bulunan
bir veri kümesinde aynı kod bozulur. Doğru düzeltme, dönüştürücüde bir çözücü
tutmak ve `parca.toString('utf8')` yerine `cozucu.write(parca)` yazmaktır.

#### İkili Kayıt Biçimi

Arabellek, sayısal alanları belirli genişlik ve bayt sırasıyla okuyup yazan yöntemler
sunar. Bu, ağ üzerinden gelen ikili bir protokolü ayrıştırmanın ve yer kazanmak için
kendi biçiminizi tanımlamanın yoludur.

Ölçüm kaydını altı bayta sığdıralım: bir bayt düğüm numarası, bir bayt metrik
numarası, dört bayt tek duyarlıklı kayan noktalı değer.

```js
// arabellek-ikili.mjs
// Bir olcumu 6 bayta sigdiran ikili bicim:
// [0] dugum no (uint8)  [1] metrik no (uint8)  [2..5] deger (float32, buyuk uclu)
function kodla(dugumNo, metrikNo, deger) {
  const ab = Buffer.alloc(6);          // sifirlanmis bellek
  ab.writeUInt8(dugumNo, 0);
  ab.writeUInt8(metrikNo, 1);
  ab.writeFloatBE(deger, 2);
  return ab;
}

function coz(ab) {
  return {
    dugumNo: ab.readUInt8(0),
    metrikNo: ab.readUInt8(1),
    deger: Number(ab.readFloatBE(2).toFixed(2)),
  };
}

const kayit = kodla(1, 0, 21.4);
console.log('ikili gosterim :', kayit.toString('hex'));
console.log('cozulmus kayit :', coz(kayit));

const kucukUclu = Buffer.alloc(4);
kucukUclu.writeFloatLE(21.4, 0);
console.log('kucuk uclu     :', kucukUclu.toString('hex'));
console.log('buyuk uclu     :', kayit.subarray(2).toString('hex'));

const metinBoyu = Buffer.byteLength(JSON.stringify({ dugum: 'kenar-01', metrik: 'sicaklik', deger: 21.4 }));
console.log(`metin gosterimi ${metinBoyu} bayt, ikili gosterim ${kayit.length} bayt`);
```

```sh
node arabellek-ikili.mjs
```

```
ikili gosterim : 010041ab3333
cozulmus kayit : { dugumNo: 1, metrikNo: 0, deger: 21.4 }
kucuk uclu     : 3333ab41
buyuk uclu     : 41ab3333
metin gosterimi 53 bayt, ikili gosterim 6 bayt
```

Yöntem adlarındaki `BE` ve `LE` sonekleri, Bilgisayarlar Nasıl Çalışır kursunda
tanıtılan büyük uçlu ve küçük uçlu yerleşimi seçer. Çıktının üçüncü ve dördüncü
satırları aynı sayının iki yerleşimini yan yana gösteriyor: bayt dizisi ters. Ağ
protokollerinde geleneksel seçim büyük uçlu yerleşimdir; bir biçim tanımlarken hangi
uçluluğun kullanıldığı yazılmalıdır, yoksa iki uç birbirinin verisini yanlış okur.

Son satır, ikili biçimin neden var olduğunu gösterir: aynı ölçüm metin olarak 53,
ikili olarak 6 bayt tutuyor. Karşılığında ikili biçim insan tarafından okunamaz,
kendini tanımlamaz ve alan eklendiğinde eski çözücüleri kırar. Bu ödünleşim
İnternet Nasıl Çalışır kursunda HTTP'nin metin tabanlı olması gerekçelendirilirken
tartışılmıştı; seçim, hacim ile incelenebilirlik arasındadır.

Değeri dört bayta sıkıştırmanın bir bedeli daha vardır. Tek duyarlıklı kayan noktalı
gösterim, yaklaşık yedi anlamlı ondalık basamak taşır; `21.4` değeri bu gösterimde
tam olarak temsil edilemez. Örnekte `toFixed(2)` ile yuvarlanmasının nedeni budur.
Ölçüm duyarlığı bu sınırın üstündeyse çift duyarlıklı yazma yöntemleri kullanılır.

Arabellek yöntemleri sınır denetimi yapar: dizinin dışına yazmaya çalışan bir çağrı
`RangeError` fırlatır. Bu, Bilgisayarlar Nasıl Çalışır kursunda anlatılan sınırsız
bellek erişimi sınıfındaki hataları çalışma zamanı düzeyinde engeller.

#### Özet

- Arabellek, `Uint8Array` üzerine kurulmuş ham bayt dizisidir; karakter sayısı ile
  bayt sayısı çok baytlı kodlamalarda ayrışır ve protokol başlıklarına bayt sayısı
  yazılır.
- `Buffer.alloc` sıfırlanmış bellek verir; sıfırlamayı atlayan ayırma, tamamen
  doldurulmadığında eski bellek içeriğini sızdırır.
- `subarray` kopya değil görünüm döndürür; bağımsız kopya `Buffer.from` ile açıkça
  istenir.
- Çok baytlı bir karakter parça sınırında bölündüğünde doğrudan çözme veriyi bozar;
  `node:string_decoder` yarım baytları saklayarak bunu önler.
- Sayısal alanlar genişlik ve uçluluk seçilerek okunup yazılır; ikili biçim yer
  kazandırır, incelenebilirlik ve genişletilebilirlikten ödün verir.

#### Sonraki Adım

Buraya kadar akışların olay yaydığından söz edildi ama olay düzeneğinin kendisi
açılmadı. Ölçüm toplayıcının bir sonraki adımı, veri geldiğinde ilgilenen tarafları
haberdar eden bir yapı gerektiriyor: eşik aşıldığında uyarı üreten, bozuk satırı
bildiren, kaynağın kapandığını duyuran bir düzenek. Sonraki ders bu düzeneğin temel
sınıfını — olay yayıcıyı — ele alır.

### Olay Yayıcı

Source: https://academia.sh/tr/kurslar/nodejs/olay-yayici

Gözlemci kalıbının yerleşik gerçekleştirimi, yayımın eşzamanlılığı, hata olayının ayrıcalıklı davranışı, dinleyici sınırı uyarısı ve olayların söz biçimiyle beklenmesi.

Akış dersinde geçen `data`, `end` ve `drain` adları tek bir düzenekten gelir. Aynı
düzenek HTTP sunucusunda, alt süreçlerde ve süreç nesnesinin sinyal bildirimlerinde
de kullanılır: çalışma zamanının eşzamansız arayüzlerinin çoğu bu tek sınıfın
üstüne kurulmuştur.

Bu ders o sınıfı ele alır ve ölçüm toplayıcının veri kaynağını olay tabanlı hâle
getirir: kayıt geldiğinde haber veren, eşik aşıldığında uyaran, bozuk satırı
bildiren bir kaynak.

#### Gözlemci Kalıbının Yerleşik Gerçekleştirimi

`node:events` modülündeki `EventEmitter` sınıfı, ad başına dinleyici listesi tutar.
`on` ile listeye eklenir, `emit` ile liste sırayla çağrılır. Bu, Programlama
Temelleri kursundaki yüksek mertebeden fonksiyon kavramının doğrudan uygulamasıdır:
davranış, bir değer olarak saklanır ve zamanı gelince çağrılır.

Kalıbın kazancı, üretici ile tüketici arasındaki bağı gevşetmesidir. Kaynak, kimin
dinlediğini bilmez; dinleyici, kaynağın iç işleyişini bilmez. Aralarındaki tek
sözleşme olay adı ve yükün biçimidir.

```js
// yayici.mjs
import { EventEmitter } from 'node:events';

class OlcumKaynagi extends EventEmitter {
  besle(satir) {
    try {
      const kayit = JSON.parse(satir);
      this.emit('olcum', kayit);
      if (kayit.metrik === 'sicaklik' && kayit.deger > 24) {
        this.emit('esik-asildi', kayit);
      }
    } catch (hata) {
      this.emit('hata-satiri', { satir, hata });
    }
  }
}

const kaynak = new OlcumKaynagi();

kaynak.on('olcum', (k) => console.log('olcum      :', k.dugum, k.metrik, k.deger));
kaynak.once('esik-asildi', (k) => console.log('esik (bir kez):', k.dugum, k.deger));
kaynak.on('hata-satiri', ({ satir }) => console.log('bozuk satir:', satir));

kaynak.besle('{"dugum":"kenar-01","metrik":"sicaklik","deger":21.4}');
kaynak.besle('{"dugum":"kenar-03","metrik":"sicaklik","deger":24.1}');
kaynak.besle('{"dugum":"kenar-03","metrik":"sicaklik","deger":24.6}');
kaynak.besle('bozuk');

console.log('olcum dinleyici sayisi:', kaynak.listenerCount('olcum'));
console.log('esik dinleyici sayisi :', kaynak.listenerCount('esik-asildi'));
```

```sh
node yayici.mjs
```

```
olcum      : kenar-01 sicaklik 21.4
olcum      : kenar-03 sicaklik 24.1
esik (bir kez): kenar-03 24.1
olcum      : kenar-03 sicaklik 24.6
bozuk satir: bozuk
olcum dinleyici sayisi: 1
esik dinleyici sayisi : 0
```

`once` ile eklenen dinleyici ilk yayımdan sonra listeden çıkarılır; çıktının son
satırındaki sıfır bunu doğruluyor. İkinci eşik aşımında uyarı yazılmamış olması da
aynı nedenledir. Kalıcı dinleyici için `on`, tek seferlik olan için `once` kullanılır;
elle kaldırmak gerektiğinde `off` çağrısına dinleyicinin **aynı referansı** verilmelidir
— adsız bir fonksiyon ifadesiyle eklenen dinleyici bir daha kaldırılamaz.

#### Yayım Eşzamanlıdır

Ad "olay" olduğu için bir kuyruğa girdiği sanılabilir. Öyle değildir: `emit`,
dinleyicileri eklenme sırasına göre **o anda** çağırır ve hepsi bitince döner.

```js
// yayici-senkron.mjs
import { EventEmitter } from 'node:events';
const y = new EventEmitter();
y.on('olay', () => console.log('2 dinleyici'));
console.log('1 emit oncesi');
y.emit('olay');
console.log('3 emit sonrasi');
```

```sh
node yayici-senkron.mjs
```

```
1 emit oncesi
2 dinleyici
3 emit sonrasi
```

Bunun iki sonucu vardır. Birincisi, dinleyici içinde yapılan uzun senkron iş, olay
döngüsünü diğer her şey gibi durdurur; olay yaymak işi ertelemez. İkincisi, dinleyici
içinde fırlatılan bir hata `emit` çağrısının yapıldığı yere doğru yayılır — kaynak
kodunun ortasına, hiç beklemediği bir yere.

Bir dinleyicinin işini erteleyerek çağıranı serbest bırakması gerekiyorsa erteleme
açıkça yazılır: iş bir zamanlayıcıya veya mikro göreve bırakılır. Kaynağın kendisi bu
kararı veremez.

#### Hata Olayının Ayrıcalığı

`error` adı, sınıf içinde özel olarak ele alınır. Bu ada dinleyici yokken yayım
yapılırsa, yük fırlatılır ve yakalanmazsa süreç sonlanır.

```js
// yayici-hata.mjs
import { EventEmitter } from 'node:events';

const dinleyicili = new EventEmitter();
dinleyicili.on('error', (h) => console.log('yakalandi:', h.message));
dinleyicili.emit('error', new Error('kaynak kapandi'));

const dinleyicisiz = new EventEmitter();
dinleyicisiz.emit('error', new Error('bu firlatilacak'));
console.log('bu satira ulasilmaz');
```

Standart çıktıya yalnızca tek bir satır düşer:

```sh
node yayici-hata.mjs 2>/dev/null
```

```
yakalandi: kaynak kapandi
```

Süreç ikinci yayımda sonlanır; `bu satira ulasilmaz` satırı yazılmaz ve standart
hataya bir yığıt izi düşer. İzin içeriği dosya yollarını taşıdığı için makineye göre
değişir.

Bu davranış bilinçli bir tasarımdır. Eşzamansız bir kaynağın hatası sessizce
yutulursa, program yanlış veriyle çalışmaya devam eder ve arıza çok sonra, ilgisiz
bir yerde görünür. Kural şudur: **hata yayabilen her nesneye bir `error` dinleyicisi
takılır.** Akış nesneleri de birer olay yayıcı olduğundan bu kural onlar için de
geçerlidir; önceki derste `pipeline` kullanmanın gerekçesi de buydu: zincirdeki
hatayı tek bir yerden bildirir ve kalan akışları kendisi yok eder.

#### Dinleyici Sınırı

Bir ada eklenen dinleyici sayısı varsayılan sınırı aştığında çalışma zamanı bir uyarı
yazar:

```js
// yayici-sinir.mjs
import { EventEmitter } from 'node:events';
const y = new EventEmitter();
for (let i = 0; i < 11; i += 1) y.on('olcum', () => {});
console.log('dinleyici sayisi:', y.listenerCount('olcum'));
```

```sh
node yayici-sinir.mjs
```

{/* mask: (?<=\(node:)[0-9]+(?=\)) */}
```
dinleyici sayisi: 11
(node:59045) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 olcum listeners added to [EventEmitter]. MaxListeners is 10. Use emitter.setMaxListeners() to increase limit
```

Uyarı satırının başındaki sayı süreç kimliğidir ve her çalıştırmada değişir. Uyarı
bir hata değildir; program çalışmaya devam eder.

Uyarının gerekçesi şudur: dinleyici listesi büyümeye devam ediyorsa, bu genellikle
her istekte yeni dinleyici eklendiği ve hiç kaldırılmadığı anlamına gelir. Liste
büyüdükçe kapatılan fonksiyonların tuttuğu değerler de serbest bırakılamaz ve bellek
sızıntısı oluşur — Eşzamansız JavaScript ve Çalışma Zamanı kursunda erişilebilirlik
temelli çöp toplama anlatılırken kurulan durum tam olarak budur.

Gerçekten çok dinleyici gerekiyorsa sınır `setMaxListeners` ile yükseltilir. Sınırı
yükseltmeden önce, listenin neden büyüdüğü açıklanmalıdır.

#### Olayları Söz Olarak Beklemek

Olay tabanlı arayüzü `async`/`await` ile birleştirmek için modül iki yardımcı sunar.
`once` fonksiyonu tek bir olayı bekleyen söz döndürür; `on` fonksiyonu ise olayları
eşzamansız yinelenebilir hâle getirir.

```js
// yayici-soz.mjs
import { EventEmitter, once, on } from 'node:events';

const kaynak = new EventEmitter();

// Tek bir olayi beklemek: soz olarak
setTimeout(() => kaynak.emit('hazir', { dugum: 'kenar-01' }), 20);
const [yuk] = await once(kaynak, 'hazir');
console.log('once ile beklendi:', yuk);

// Surekli akan olaylari yinelemek
setTimeout(() => {
  kaynak.emit('olcum', 21.4);
  kaynak.emit('olcum', 21.9);
  kaynak.emit('bitti');
}, 20);

const durdurucu = new AbortController();
kaynak.once('bitti', () => durdurucu.abort());

try {
  for await (const [deger] of on(kaynak, 'olcum', { signal: durdurucu.signal })) {
    console.log('akan olay        :', deger);
  }
} catch (hata) {
  if (hata.name !== 'AbortError') throw hata;
  console.log('yineleme sonlandi:', hata.name);
}
```

```sh
node yayici-soz.mjs
```

```
once ile beklendi: { dugum: 'kenar-01' }
akan olay        : 21.4
akan olay        : 21.9
yineleme sonlandi: AbortError
```

Her iki yardımcı da dinleyici argümanlarını dizi olarak verir; tek argümanlı
olaylarda çözümleme ile ilk öğe alınır.

İkinci bölümdeki durdurucu, sonsuz yinelemeyi bitirmenin yoludur. `on` ile kurulan
döngü kendiliğinden sonlanmaz — kaynak olay yaymayı bıraksa bile döngü beklemeye
devam eder. Sonlandırma bir iptal işaretiyle yapılır ve iptal, yineleyiciden bir
`AbortError` olarak çıkar. Bu, Eşzamansız JavaScript ve Çalışma Zamanı kursunda
istek iptali için tanıtılan aynı düzenektir.

Bir uyarı: `on` ile kurulan yineleme, döngü gövdesi çalışırken gelen olayları
biriktirir. Gövde yavaşsa bu birikim sınırsızdır; olay yayıcı, akışların aksine geri
basınç taşımaz. Yüksek hacimli kaynaklarda olay yayıcı yerine akış kullanılmasının
nedeni budur.

#### Özet

- Olay yayıcı, ad başına dinleyici listesi tutan gözlemci kalıbı gerçekleştirimidir;
  çalışma zamanının eşzamansız arayüzlerinin çoğu bu sınıfın üstüne kuruludur.
- `emit` eşzamanlıdır: dinleyicileri eklenme sırasına göre o anda çağırır ve hepsi
  bitince döner; yayım işi ertelemez.
- `error` adı ayrıcalıklıdır; dinleyicisiz yayım yükü fırlatır ve süreci sonlandırır.
  Hata yayabilen her nesneye dinleyici takılır.
- Dinleyici sayısı sınırı aştığında yazılan uyarı, çoğunlukla kaldırılmayan dinleyici
  birikimini ve buna bağlı bellek sızıntısını haber verir.
- `once` ve `on` yardımcıları olayları söz ve eşzamansız yineleme biçimine çevirir;
  yineleme bir iptal işaretiyle sonlandırılır ve geri basınç taşımaz.

#### Sonraki Adım

Ölçüm toplayıcının bütün parçaları hazır: dosyadan akışla okuma, kayıt kurma,
özetleme ve olay tabanlı bildirim. Eksik olan tek şey, özetin dışarıya sunulmasıdır.
Sonraki ders HTTP sunucusunu kurar: isteğin okunabilir, yanıtın yazılabilir bir akış
olduğu; İnternet Nasıl Çalışır kursunda satır satır incelenen istek ve yanıt
yapısının kodda nasıl karşılandığı ele alınacak.

### HTTP Sunucusu

Source: https://academia.sh/tr/kurslar/nodejs/http-sunucusu

İstek ve yanıt nesnelerinin akış karşılığı, yol ve yöntem üzerinden dağıtım, gövde okumada boyut sınırı, uzunluk bildirimi ile parçalı aktarım arasındaki seçim.

İnternet Nasıl Çalışır kursu bir HTTP isteğini satır satır çözümledi: istek satırı,
başlıklar, boş satır, gövde. Orada bu metni okuyan taraf bir örnek programdı ve
protokolün ayrıştırılması gösterilmemişti.

Bu ders o işi yerleşik modüle devreder ve ölçüm toplayıcıyı bir hizmete dönüştürür.
Anahtar gözlem şudur: istek nesnesi okunabilir, yanıt nesnesi yazılabilir bir akıştır.
Akışlar dersinde kurulan her şey burada doğrudan geçerlidir.

#### Özet Birikimini Ayrı Modüle Almak

Hizmet kodunu yazmadan önce, kayıt doğrulama ve özet birikimi ayrı bir modüle
alınmalıdır. Ağdan gelen veriyle dosyadan gelen veri aynı yoldan geçmeli ve bu yol
sunucudan bağımsız sınanabilmelidir.

```js
// ozetleyici.mjs
export function satirdanKayit(satir) {
  const kayit = JSON.parse(satir);
  if (typeof kayit.dugum !== 'string' || typeof kayit.metrik !== 'string') {
    throw new TypeError('dugum ve metrik dizgi olmali');
  }
  if (!Number.isFinite(kayit.deger)) {
    throw new TypeError('deger sonlu bir sayi olmali');
  }
  return kayit;
}

export class Ozetleyici {
  #kovalar = new Map();

  ekle({ dugum, metrik, deger }) {
    const anahtar = `${dugum}/${metrik}`;
    const kova = this.#kovalar.get(anahtar) ?? { sayi: 0, toplam: 0, enBuyuk: -Infinity };
    kova.sayi += 1;
    kova.toplam += deger;
    kova.enBuyuk = Math.max(kova.enBuyuk, deger);
    this.#kovalar.set(anahtar, kova);
  }

  ozet() {
    return [...this.#kovalar].map(([anahtar, { sayi, toplam, enBuyuk }]) => ({
      anahtar,
      sayi,
      ortalama: Number((toplam / sayi).toFixed(2)),
      enBuyuk,
    }));
  }
}
```

Doğrulama fonksiyonu yalnızca ayrıştırmakla kalmaz, alan türlerini de sınar. Ağdan gelen
veriye güvenilmez: `deger` alanı dizgi geldiğinde toplama işlemi sessizce birleştirmeye
döner ve özet anlamsızlaşır.

#### Sunucu ve İşleyici

`node:http` modülündeki `createServer`, her istek için çağrılan bir işleyici alır.
İşleyicinin iki argümanı vardır ve her ikisi de akıştır.

Sunucu, kursun çalışma dosyası olan `olcumler.ndjson` dosyasını açılışta okur. Dosya çalışma dizininizde yoksa aşağıdaki komut onu oluşturur; içerik, çalışma zamanı modeli konusunda verilen on iki satırın aynısıdır:

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

```js
// hizmet.mjs
import { createServer } from 'node:http';
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
import { pipeline } from 'node:stream/promises';
import { Ozetleyici, satirdanKayit } from './ozetleyici.mjs';

const ozetleyici = new Ozetleyici();

// Acilista dosyayi akisla okuyup kovalari doldur
async function baslangicVerisiniYukle(dosya) {
  const satirlar = createInterface({
    input: createReadStream(dosya),
    crlfDelay: Infinity,
  });
  let sayac = 0;
  for await (const satir of satirlar) {
    if (satir.trim() === '') continue;
    ozetleyici.ekle(satirdanKayit(satir));
    sayac += 1;
  }
  return sayac;
}

async function govdeyiOku(istek, enCokBayt = 64 * 1024) {
  const parcalar = [];
  let boyut = 0;
  for await (const parca of istek) {          // istek okunabilir bir akistir
    boyut += parca.length;
    if (boyut > enCokBayt) {
      istek.destroy();
      throw Object.assign(new Error('govde cok buyuk'), { durum: 413 });
    }
    parcalar.push(parca);
  }
  return Buffer.concat(parcalar).toString('utf8');
}

function jsonYaz(yanit, durum, deger) {
  const govde = JSON.stringify(deger) + '\n';
  yanit.writeHead(durum, {
    'content-type': 'application/json; charset=utf-8',
    'content-length': Buffer.byteLength(govde),
  });
  yanit.end(govde);
}

const sunucu = createServer(async (istek, yanit) => {
  const adres = new URL(istek.url, `http://${istek.headers.host}`);
  try {
    if (istek.method === 'GET' && adres.pathname === '/ozet') {
      jsonYaz(yanit, 200, ozetleyici.ozet());
      return;
    }
    if (istek.method === 'GET' && adres.pathname === '/ham') {
      yanit.writeHead(200, { 'content-type': 'application/x-ndjson; charset=utf-8' });
      await pipeline(createReadStream('olcumler.ndjson'), yanit);   // govde akisla yazilir
      return;
    }
    if (istek.method === 'POST' && adres.pathname === '/olcum') {
      const govde = await govdeyiOku(istek);
      let sayac = 0;
      for (const satir of govde.split('\n')) {
        if (satir.trim() === '') continue;
        ozetleyici.ekle(satirdanKayit(satir));
        sayac += 1;
      }
      jsonYaz(yanit, 201, { alinan: sayac });
      return;
    }
    jsonYaz(yanit, 404, { hata: 'bulunamadi' });
  } catch (hata) {
    jsonYaz(yanit, hata.durum ?? 400, { hata: hata.message });
  }
});

const yuklenen = await baslangicVerisiniYukle('olcumler.ndjson');
sunucu.listen(8791, '127.0.0.1', () => {
  const { address, port } = sunucu.address();
  console.log(`${yuklenen} kayit yuklendi; dinleniyor: http://${address}:${port}`);
});
```

```sh
node hizmet.mjs
```

```
12 kayit yuklendi; dinleniyor: http://127.0.0.1:8791
```

Örnekteki 8791 sayısı bir seçimdir; makinenizde kullanılan bir bağlantı noktası varsa
başka bir sayı yazın. Sonraki derste bu değer yapılandırmadan okunacak.

`listen` çağrısının ikinci argümanı olan `127.0.0.1`, sunucunun yalnızca geri döngü
arayüzünü dinlemesini sağlar; bu adres İnternet Nasıl Çalışır kursunda tanıtılmıştı.
Argüman verilmediğinde sunucu bütün arayüzlerden erişilebilir olur — geliştirme
sırasında istenmeyen bir açıklıktır.

#### İsteği Ayrıştırmak

İstek nesnesi, protokolün ayrıştırılmış hâlini alan olarak sunar. `istek.method`
istek satırının ilk alanı, `istek.url` ikinci alanı, `istek.headers` ise küçük harfe
çevrilmiş başlık adlarından oluşan bir nesnedir.

`istek.url` yalnızca yol ve sorgu bölümünü taşır; alan adı `host` başlığındadır.
Bu ayrım, İnternet Nasıl Çalışır kursunda ada dayalı sanal barındırmanın dayanağı
olarak açıklanmıştı. İki parçayı birleştirip `URL` nesnesi kurmak, yol ile sorguyu
elle ayırmaktan daha güvenlidir: kaçış çözme ve normalleştirme kuralları nesnenin
içindedir.

Gövde, alanlarda bulunmaz — akıştır. Bunun nedeni, gövdenin keyfi büyüklükte
olabilmesidir. Yukarıdaki `govdeyiOku`, `for await` ile parçaları toplarken bir
sınır da uygular. Sınır zorunludur: sınırsız gövde okuyan bir uç, tek bir istekle
sürecin belleğini tüketebilir.

#### Yanıtı Yazmak

Yanıt nesnesi yazılabilir bir akıştır. `writeHead` durum kodunu ve başlıkları
belirler; `end` gövdeyi yazıp yanıtı kapatır.

Aşağıdaki `curl` çıktıları, sunucu ayrı bir uçbirimde çalışırken alınmış örnek koşumlardır.

{/* run: skip */}
```sh
curl -s -i http://127.0.0.1:8791/ozet
```

```
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
content-length: 420
Date: Mon, 27 Jul 2026 04:54:03 GMT
Connection: keep-alive
Keep-Alive: timeout=5

[{"anahtar":"kenar-01/sicaklik","sayi":3,"ortalama":21.87,"enBuyuk":22.3},{"anahtar":"kenar-01/nem","sayi":2,"ortalama":47.6,"enBuyuk":48},{"anahtar":"kenar-02/sicaklik","sayi":3,"ortalama":20.23,"enBuyuk":20.7},{"anahtar":"kenar-02/nem","sayi":1,"ortalama":52.5,"enBuyuk":52.5},{"anahtar":"kenar-03/sicaklik","sayi":2,"ortalama":24.35,"enBuyuk":24.6},{"anahtar":"kenar-03/nem","sayi":1,"ortalama":41.3,"enBuyuk":41.3}]
```

`Date` başlığındaki değer isteğin anına göre değişir; `Connection` ve `Keep-Alive`
başlıkları da istemcinin pazarlığına bağlıdır. Sabit olan, işleyicinin yazdığı iki
başlık ile gövdedir.

`content-length` başlığı burada elle hesaplandı. Karakter sayısı değil bayt sayısı
yazılmalıdır; arabellekler dersindeki ayrım tam olarak bu yüzden önemliydi.

Uzunluğu önceden bilmediğiniz yanıtlarda bu başlık yazılmaz. Gövde akışla
yazıldığında çalışma zamanı parçalı aktarıma geçer:

{/* run: skip */}
```sh
curl -s -i http://127.0.0.1:8791/ham | head -8
```

```
HTTP/1.1 200 OK
content-type: application/x-ndjson; charset=utf-8
Date: Mon, 27 Jul 2026 04:54:03 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
```

`Transfer-Encoding: chunked` başlığı, gövdenin her parçasının önüne kendi uzunluğunun
yazıldığını bildirir; alıcı sonu bu yolla anlar. İnternet Nasıl Çalışır kursunda
"uzunluk önceden bilinmiyorsa gövde parçalar hâlinde gönderilir" diye anlatılan
düzenek budur.

Seçim şöyle yapılır: yanıt küçük ve bellekte hazırsa uzunluk bildirmek istemciye
ilerleme bilgisi verir; yanıt büyük veya üretilirken akıyorsa parçalı aktarım, belleği
yanıt boyunca meşgul etmemeyi sağlar. `pipeline` kullanılması, geri basıncın
uygulanmasını da güvence altına alır: istemci yavaşsa dosya okuması da yavaşlar.

#### Veri Göndermek ve Hatalar

Ölçüm göndermek gövdeli bir istekle yapılır:

{/* run: skip */}
```sh
curl -s -X POST --data-binary '{"dugum":"kenar-01","metrik":"sicaklik","deger":25.9}' -w 'durum: %{http_code}\n' http://127.0.0.1:8791/olcum
```

```
{"alinan":1}
durum: 201
```

201 kodu, İnternet Nasıl Çalışır kursundaki sınıflandırmaya göre "yeni kaynak
oluşturuldu" anlamına gelir; 200'den ayrı seçilmesi isteğin etkisini bildirir.

Bozuk gövde farklı bir sınıfa düşer:

{/* run: skip */}
```sh
curl -s -X POST --data-binary 'bozuk' -w 'durum: %{http_code}\n' http://127.0.0.1:8791/olcum
```

```
{"hata":"Unexpected token 'b', \"bozuk\" is not valid JSON"}
durum: 400
```

{/* run: skip */}
```sh
curl -s -w 'durum: %{http_code}\n' http://127.0.0.1:8791/yok
```

```
{"hata":"bulunamadi"}
durum: 404
```

400 sınıfı isteği gönderenin hatasını, 500 sınıfı sunucunun hatasını bildirir. Bu
ayrımı doğru yapmak yalnızca nezaket değil, işletim gereğidir: 500 oranı bir arıza
göstergesidir, 400 oranı ise istemci davranışının göstergesi. İkisi karışırsa
gösterge anlamını yitirir.

Yukarıdaki işleyicideki hata yakalayıcının bir eksiği vardır: her yakalanan hatayı
400 olarak bildiriyor, oysa özetleme sırasında oluşan bir programcı hatası 500
olmalıdır. Bu ayrım Hata Yönetimi Stratejisi dersinde düzeltilecek.

Bir uyarı: işleyici eşzamansız olduğu için, içinde oluşan ve yakalanmayan bir hata
`createServer` tarafından görülmez. `try`/`catch` bloğunun bütün gövdeyi sarması bu
yüzden gereklidir — sarmazsa yanıt hiç yazılmaz ve istemci zaman aşımına uğrar.

#### Özet

- İstek nesnesi okunabilir, yanıt nesnesi yazılabilir akıştır; akışlar dersindeki
  kuralların tümü burada geçerlidir.
- İstek satırı ve başlıklar ayrıştırılmış alanlar olarak sunulur; `istek.url` yalnızca
  yol ve sorguyu taşır, alan adı `host` başlığındadır.
- Gövde akış olduğundan boyutu sınırlanmalıdır; sınırsız okuma tek istekle belleği
  tüketebilir.
- Uzunluk bildiren yanıtta bayt sayısı yazılır; uzunluk bilinmiyorsa çalışma zamanı
  parçalı aktarıma geçer ve akışla yazma geri basıncı korur.
- Durum kodu sınıfı, hatanın kaynağını bildirir; istemci ve sunucu hatalarının
  karışması işletim göstergelerini anlamsızlaştırır.

#### Sonraki Adım

Hizmet tek bir süreçte çalışıyor ve bütün işi tek iş parçacığında yapıyor. Ölçüm
verisi üzerinde pahalı bir hesap gerektiğinde — örneğin uzun bir dönemin yeniden
özetlenmesi — bu tek iş parçacığı bütün istekleri bekletir. Sonraki ders sürecin
dışına iş çıkarmanın ilk yolunu ele alır: alt süreç başlatmak, onunla akışlar
üzerinden konuşmak ve dış komutları güvenli çağırmak.

### Alt Süreçler

Source: https://academia.sh/tr/kurslar/nodejs/alt-surecler

Dış komut çalıştırmanın iki yolu, kabuk katmanının getirdiği risk, çıkış kodu ile sonlandıran sinyalin ayrımı ve çalışma zamanı süreçleri arasında ileti geçirme.

Önceki ders hizmeti tek bir süreçte kurdu. Bir sürecin yapabileceği iş sınırlıdır ve
bazı işleri hiç yapmaması gerekir: sistemde zaten var olan bir aracın işini yeniden
yazmak, o aracın yıllarca sınanmış davranışını yeniden üretmeye çalışmaktır.

Bu ders sürecin dışına iş çıkarmayı ele alır. İki ayrı ihtiyaç vardır: bir dış komutu
çalıştırmak ve aynı çalışma zamanından ikinci bir süreç başlatıp onunla konuşmak.

#### Dış Komut Çalıştırmak

`node:child_process` modülündeki `spawn`, bir programı ayrı süreç olarak başlatır ve
onun üç standart akışını nesne olarak verir. Kabuk Programlama kursundaki akış modeli
burada olduğu gibi karşılık bulur.

Bu dersin örnekleri kursun çalışma dosyası olan `olcumler.ndjson` dosyasını kullanır. Dosya çalışma dizininizde yoksa aşağıdaki komut onu oluşturur; içerik, çalışma zamanı modeli konusunda verilen on iki satırın aynısıdır:

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

```js
// alt-surec.mjs
import { spawn } from 'node:child_process';
import { once } from 'node:events';

function calistir(komut, argumanlar) {
  const cocuk = spawn(komut, argumanlar);      // kabuk yok: argumanlar dizi olarak gecer
  const cikti = [];
  const hata = [];
  cocuk.stdout.on('data', (p) => cikti.push(p));
  cocuk.stderr.on('data', (p) => hata.push(p));
  return once(cocuk, 'close').then(([kod, sinyal]) => ({
    kod,
    sinyal,
    cikti: Buffer.concat(cikti).toString('utf8').trim(),
    hata: Buffer.concat(hata).toString('utf8').trim(),
  }));
}

console.log('wc -l  :', await calistir('wc', ['-l', 'olcumler.ndjson']));
console.log('yok.txt:', await calistir('cat', ['yok.txt']));
```

```sh
node alt-surec.mjs
```

```
wc -l  : { kod: 0, sinyal: null, cikti: '12 olcumler.ndjson', hata: '' }
yok.txt: {
  kod: 1,
  sinyal: null,
  cikti: '',
  hata: 'cat: yok.txt: No such file or directory'
}
```

İki çağrının ayrımı çıkış kodundadır. Sıfır başarıyı, bir başarısızlığı bildirir;
Kabuk Programlama kursundaki kural değişmedi. Tanı iletisi ikinci akıştan geldi ve
veriyle karışmadı — aynı ayrımın çalışma zamanı tarafındaki karşılığı budur.

Parçalar arabellek olarak toplanıp sonda birleştiriliyor. Her parçayı ayrı ayrı
dizgiye çevirip eklemek, arabellekler dersinde gösterilen sınır bozulmasına yol
açardı.

#### Kabuk Katmanı ve Argüman Aktarımı

Aynı modülde `exec` adlı bir fonksiyon daha vardır. Fark, komutu bir kabuk sürecine metin
olarak vermesidir. Kabuk o metni kendi kurallarıyla yorumlar: değişken açılımı,
yol adı genişletmesi, boru hattı, noktalı virgülle komut ayırma.

Bu yorum, komut metnine dışarıdan gelen bir parça eklendiğinde sorun olur:

```js
// alt-surec-kabuk.mjs
import { execFile, exec } from 'node:child_process';
import { promisify } from 'node:util';

const execSoz = promisify(exec);
const execFileSoz = promisify(execFile);

// Disaridan gelmis, guvenilmeyen bir dosya adi
const dosyaAdi = 'olcumler.ndjson; echo ENJEKTE';

// 1. Kabugu araya sokan cagri: metin kabuk tarafindan yorumlanir
const { stdout: kabukCiktisi } = await execSoz(`wc -l ${dosyaAdi}`);
console.log('exec     :', JSON.stringify(kabukCiktisi.trim()));

// 2. Kabuk olmadan cagri: ad tek bir arguman olarak gecer
try {
  await execFileSoz('wc', ['-l', dosyaAdi]);
} catch (hata) {
  console.log('execFile :', hata.stderr.trim());
}
```

```sh
node alt-surec-kabuk.mjs
```

```
exec     : "12 olcumler.ndjson\nENJEKTE"
execFile : wc: 'olcumler.ndjson; echo ENJEKTE': No such file or directory
```

Birinci çağrıda noktalı virgülden sonraki komut da çalıştı. Dosya adı yerine
gelen metin bir hesap silme ya da veri gönderme komutu olsaydı sonuç aynı biçimde
çalıştırılırdı. İkinci çağrıda ise metnin tamamı tek bir argüman olarak geçti; `wc`
o adda bir dosya arayıp bulamadı.

Kural: **komut metnini dizgi birleştirmesiyle kurmayın.** Program adı ve argüman
dizisi ayrı verildiğinde araya kabuk girmez ve yorumlanacak bir metin oluşmaz.
Gerçekten kabuk özelliği gerekiyorsa — boru hattı veya yönlendirme gibi — sabit bir
komut metni yazılır ve değişken bölümler ortam değişkeni olarak aktarılır.

`promisify`, geri çağrılı imzayı söz döndüren imzaya çeviren yerleşik yardımcıdır.
Çalışma zamanının geri çağrılı arayüzlerinin çoğu bu dönüşümü destekler.

`exec` ve `execFile` ayrıca çıktıyı bellekte biriktirir ve bir sınırı aştığında
süreci sonlandırır. Büyük çıktı üreten komutlarda `spawn` kullanılır ve çıktı akışla
işlenir.

#### Çıkış Kodu ve Sonlandıran Sinyal

Bir alt sürecin bitişi iki farklı yolla olabilir: kendi isteğiyle çıkarak veya bir
sinyalle sonlandırılarak. `close` olayı bu iki durumu ayrı alanlarda bildirir.

```js
// alt-surec-sinyal.mjs
import { spawn } from 'node:child_process';
import { once } from 'node:events';

// Uzun surecek bir is; kendiliginden bitmesini beklemeyecegiz
const cocuk = spawn('sleep', ['30']);
console.log('cocuk baslatildi, ayri surec mi:', cocuk.pid !== process.pid);

setTimeout(() => cocuk.kill('SIGTERM'), 100);

const [kod, sinyal] = await once(cocuk, 'close');
console.log('cikis kodu:', kod, '| sonlandiran sinyal:', sinyal);
```

```sh
node alt-surec-sinyal.mjs
```

```
cocuk baslatildi, ayri surec mi: true
cikis kodu: null | sonlandiran sinyal: SIGTERM
```

Sinyalle sonlandırılan bir sürecin çıkış kodu yoktur; alan `null` gelir. Yalnızca
koda bakan bir denetim, `kod === 0` sınamasını geçemez ama `kod !== 0` sınamasını da
farklı yorumlayabilir. Doğru denetim iki alanı birlikte okur: kod sıfırsa **ve**
sinyal yoksa başarı.

Sinyal adının kendisi Kabuk Programlama kursunda tanıtılmıştı; `SIGTERM` kibar bir
sonlanma isteğidir ve süreç onu yakalayıp temizlik yapabilir. Zorla sonlandıran
sinyal yakalanamaz ve temizliğe fırsat vermez; bu yüzden yalnızca kibar istek
yanıtsız kaldığında kullanılır. Sürecin kendi kapanma davranışı Süreç Yönetimi ve
Sağlamlık dersinin konusudur.

#### Aynı Çalışma Zamanından İkinci Süreç

Ölçüm toplayıcının pahalı işi kendi kodudur, dış bir araç değil. Bunun için `fork`
kullanılır: aynı çalışma zamanının yeni bir örneğini başlatır ve iki süreç arasında
bir ileti kanalı kurar.

```js
// ozet-cocuk.mjs
import { readFile } from 'node:fs/promises';

process.on('message', async ({ dosya }) => {
  const metin = await readFile(dosya, 'utf8');
  const kovalar = new Map();
  for (const satir of metin.split('\n')) {
    if (satir.trim() === '') continue;
    const { dugum, metrik, deger } = JSON.parse(satir);
    const anahtar = `${dugum}/${metrik}`;
    const kova = kovalar.get(anahtar) ?? { sayi: 0, toplam: 0 };
    kova.sayi += 1;
    kova.toplam += deger;
    kovalar.set(anahtar, kova);
  }
  process.send({ pidVar: typeof process.pid === 'number', kovaSayisi: kovalar.size });
  process.disconnect();
});
```

```js
// ozet-ana.mjs
import { fork } from 'node:child_process';
import { once } from 'node:events';

const cocuk = fork('./ozet-cocuk.mjs');
console.log('ayri surec mi:', cocuk.pid !== process.pid);

cocuk.send({ dosya: 'olcumler.ndjson' });
const [ileti] = await once(cocuk, 'message');
console.log('cocuktan gelen:', ileti);

const [kod] = await once(cocuk, 'exit');
console.log('cocuk cikis kodu:', kod);
```

```sh
node ozet-ana.mjs
```

```
ayri surec mi: true
cocuktan gelen: { pidVar: true, kovaSayisi: 6 }
cocuk cikis kodu: 0
```

İki süreç arasında hiçbir bellek paylaşılmaz. `send` ile verilen değer dizileştirilip
kanaldan geçirilir ve karşı tarafta yeniden kurulur; dolayısıyla yalnızca
dizileştirilebilir değerler taşınabilir — fonksiyon, sınıf örneği ve dosya tanıtıcısı
doğrudan geçmez. Bu kopyalama, büyük veri için ölçülebilir bir maliyettir: çocuğa
milyonlarca kayıt göndermek yerine, dosya adını gönderip okumayı ona bırakmak
tercih edilir. Yukarıdaki örnek tam olarak bunu yapıyor.

`disconnect` çağrısı kanalı kapatır. Kanal açık kaldığı sürece iki süreç de birbirini
ayakta tutar; çocuk işini bitirdiği hâlde çıkmıyorsa nedeni genellikle budur.

Yalıtımın bir yararı daha vardır: çocuk sürecin çökmesi ana süreci düşürmez. Ana
süreç `exit` olayını görür, kararı kendisi verir — yeniden başlatabilir, isteği
hatayla yanıtlayabilir veya işi kendi yapabilir.

#### Özet

- `spawn`, programı ayrı süreç olarak başlatır ve üç standart akışını nesne olarak
  verir; büyük çıktı akışla işlenir.
- Kabuk üzerinden çalıştırma, komut metnini yorumlar; metne dışarıdan gelen bir parça
  eklendiğinde ek komut çalıştırılabilir. Program adı ve argüman dizisi ayrı verilir.
- Çıkış kodu ile sonlandıran sinyal ayrı alanlardır; sinyalle sonlandırılan sürecin
  kodu yoktur ve başarı denetimi iki alanı birlikte okur.
- `fork`, aynı çalışma zamanının ikinci örneğini başlatır ve ileti kanalı kurar;
  iletiler dizileştirilerek kopyalanır, bellek paylaşılmaz.
- Süreç yalıtımı, çocuğun çökmesini ana sürecin kararına dönüştürür.

#### Sonraki Adım

Alt süreç, işi ana iş parçacığından çıkarmanın bir yoludur ama ağır bir yoludur:
her süreç kendi belleğini ve kendi çalışma zamanı örneğini taşır, aralarındaki her
ileti kopyalanır. Sonraki ders iki alternatifi ele alır — aynı bağlantı noktasını
paylaşan süreç kümesi ve aynı süreç içinde bellek paylaşabilen iş parçacıkları — ve
hangisinin hangi iş türüne uygun olduğunu belirler.

### Küme ve İşçi İş Parçacıkları

Source: https://academia.sh/tr/kurslar/nodejs/kume-ve-isci-is-parcaciklari

Aynı bağlantı noktasını paylaşan süreç kümesi, işçi iş parçacığının bellek modeli, iki yaklaşımın hangi iş türüne uyduğu ve ölçülen engelleme farkı.

Önceki ders alt süreç başlatmayı ele aldı ve maliyetini belirtti: her süreç kendi
belleğini ve kendi çalışma zamanı örneğini taşır, aralarındaki her ileti kopyalanır.

Bu ders iki özel biçimi inceler. İlki, aynı bağlantı noktasını paylaşan süreç
kümesidir ve amacı çok çekirdekli bir makinede birden çok isteği gerçekten aynı anda
işlemektir. İkincisi, aynı süreç içinde çalışan ve bellek paylaşabilen iş
parçacıklarıdır; amacı pahalı bir hesabı ana iş parçacığından çıkarmaktır.

#### Neden Tek Süreç Yetmiyor

Olay döngüsü dersinde ölçülen sınır burada bağlayıcı hâle gelir. Tek süreç, tek bir
çekirdekte çalışan tek bir iş parçacığında istekleri işler. Girdi/çıktı ağırlıklı bir
hizmette bu yeterlidir: süreç zamanının çoğunu yoklama aşamasında bekleyerek geçirir
ve bir çekirdek onlarca eşzamanlı bağlantıya yeter.

İşin bir bölümü işlemciye bağlı olduğunda durum değişir. Makinede kaç bağımsız
yürütme birimi olduğu sorulabilir:

```js
// cekirdek.mjs
import os from 'node:os';
console.log('kullanilabilir paralellik:', os.availableParallelism());
```

```sh
node cekirdek.mjs
```

```
kullanilabilir paralellik: 12
```

Bu sayı makineye ve sürece tanınan kısıtlara göre değişir; örnek bir çalıştırmada
alınmıştır. Tek süreç, bu birimlerin yalnızca birini kullanır.

#### Süreç Kümesi

`node:cluster` modülü, bir birincil süreç ile birkaç işçi süreç kurar. İşçiler
birincil sürecin çatalladığı ayrı süreçlerdir; gelen bağlantılar **tek** bir bağlantı
noktası üzerinden aralarında dağıtılır.

```js
// kume.mjs
import cluster from 'node:cluster';
import { createServer } from 'node:http';

const ISCI_SAYISI = 2;

if (cluster.isPrimary) {
  console.log(`birincil surec ${process.pid}, ${ISCI_SAYISI} isci baslatiyor`);
  for (let i = 0; i < ISCI_SAYISI; i += 1) cluster.fork();

  cluster.on('exit', (isci, kod, sinyal) => {
    console.log(`isci ${isci.process.pid} bitti (kod ${kod}, sinyal ${sinyal}); yerine yenisi`);
    cluster.fork();
  });
} else {
  createServer((istek, yanit) => {
    yanit.writeHead(200, { 'content-type': 'text/plain' });
    yanit.end(`istegi karsilayan isci: ${process.pid}\n`);
  }).listen(8791, '127.0.0.1', () => {
    console.log(`isci ${process.pid} dinliyor`);
  });
}
```

Sunucuyu bir uçbirimde çalıştırıp başka bir uçbirimden dört istek gönderdiğimizde:

```sh
node kume.mjs
```

{/* run: skip */}
```sh
for i in 1 2 3 4; do curl -s http://127.0.0.1:8791/; done
```

```
birincil surec 96467, 2 isci baslatiyor
isci 96470 dinliyor
isci 96469 dinliyor
istegi karsilayan isci: 96470
istegi karsilayan isci: 96469
istegi karsilayan isci: 96470
istegi karsilayan isci: 96469
```

Süreç kimlikleri her çalıştırmada değişir; iki işçinin hangi sırayla dinlemeye
başladığı da değişir. Sabit olan, isteklerin iki farklı süreç tarafından
karşılanmasıdır — dağılımın örnekteki gibi düzgün sırayla olması güvence altında
değildir.

Aynı bağlantı noktasını iki sürecin dinleyebilmesi, İnternet Nasıl Çalışır kursunda
tanıtılan dinleyen soket kavramına dayanır. Dağıtımın iki yolu vardır. Windows dışındaki
platformlarda varsayılan olan döngüsel yolda bağlantıyı birincil süreç kabul eder ve
sırayla işçilere devreder; ikinci yolda birincil süreç dinleyen soketin tanıtıcısını
işçilere verir ve her işçi bağlantıyı doğrudan kendisi kabul eder. Bağlantı noktası
sayısı iki durumda da bir tanedir; onu kullanan süreç sayısı birden çoktur.

`exit` olayına bağlanan geri çağrı, düşen işçinin yerine yenisini koyar. Bu, sağlamlık
açısından kümenin asıl kazancıdır: bir işçide oluşan ölümcül hata hizmeti bütünüyle
durdurmaz. Yeniden başlatmanın sonsuz döngüye girmemesi için hızının sınırlanması
gerekir; işçi her açılışta hemen çöküyorsa yenisini açmak arızayı gizler.

Kümenin temel kısıtı, işçiler arasında **bellek paylaşılmamasıdır.** Ölçüm
toplayıcının kova haritası her işçide ayrı ayrı birikir ve `/ozet` yanıtı isteği
karşılayan işçiye göre değişir. Durum tutan bir hizmeti kümelemek, durumu süreç
dışına — paylaşılan bir depoya — taşımayı gerektirir.

#### İşçi İş Parçacıkları

`node:worker_threads` modülü, aynı süreç içinde ayrı bir iş parçacığı başlatır. Her
iş parçacığının kendi olay döngüsü ve kendi öbeği vardır; bellek varsayılan olarak
paylaşılmaz ama paylaşılabilir bir arabellek üzerinden açıkça paylaştırılabilir.

Pahalı bir hesabı iş parçacığına verelim:

{/* run: fail */}
```js
// agir-is.mjs
import { parentPort, workerData } from 'node:worker_threads';

// Olcum degerlerinin ustunde bilerek pahali bir hesap
function agirToplam(yineleme) {
  let toplam = 0;
  for (let i = 0; i < yineleme; i += 1) toplam += Math.sqrt(i) % 1;
  return toplam;
}

parentPort.postMessage(Number(agirToplam(workerData.yineleme).toFixed(3)));
```

Bu dosya `node agir-is.mjs` ile tek başına çalıştırılmaz: `parentPort` yalnızca iş parçacığı olarak başlatılmış kodda dolu gelir. Dosyayı aşağıdaki ana betik başlatır.

```js
// isci-ana.mjs
import { Worker } from 'node:worker_threads';
import { once } from 'node:events';

const YINELEME = 3e8;

setTimeout(() => console.log('2 ana is parcacigi bos: zamanlayici calisti'), 0);
console.log('1 isci baslatildi');

const isci = new Worker('./agir-is.mjs', { workerData: { yineleme: YINELEME } });
const [sonuc] = await once(isci, 'message');
console.log('3 isciden gelen sonuc:', sonuc);
await once(isci, 'exit');
```

```sh
node isci-ana.mjs
```

```
1 isci baslatildi
2 ana is parcacigi bos: zamanlayici calisti
3 isciden gelen sonuc: 149989897.293
```

Aynı hesap ana iş parçacığında yapıldığında sıra değişir:

```js
// isci-yok.mjs
const YINELEME = 3e8;

function agirToplam(yineleme) {
  let toplam = 0;
  for (let i = 0; i < yineleme; i += 1) toplam += Math.sqrt(i) % 1;
  return toplam;
}

setTimeout(() => console.log('3 zamanlayici ancak simdi calisti'), 0);
console.log('1 hesap basliyor');
console.log('2 sonuc:', Number(agirToplam(YINELEME).toFixed(3)));
```

```sh
node isci-yok.mjs
```

```
1 hesap basliyor
2 sonuc: 149989897.293
3 zamanlayici ancak simdi calisti
```

İki çıktının farkı ölçünün kendisidir. İlkinde zamanlayıcı hesap sürerken çalıştı;
ana iş parçacığı boştu ve o sırada gelen bir isteği işleyebilirdi. İkincisinde
zamanlayıcı hesabın bitmesini bekledi — bir sunucuda bu, bütün eşzamanlı isteklerin
beklemesi demektir.

Sonuç değeri iki çalıştırmada aynı çıkıyor; hesap belirlenimcidir ve iş parçacığına
taşınması sonucu değiştirmez.

#### Hangisi Ne Zaman

İki yaklaşım birbirinin yerine geçmez.

| Ölçüt | Süreç kümesi | İşçi iş parçacığı |
|---|---|---|
| Yalıtım | tam; biri çökerse diğeri sürer | zayıf; ölümcül hata süreci düşürebilir |
| Bellek | ayrı; her süreç kendi örneğini taşır | ortak süreç, ayrı öbek; paylaşılabilir arabellek olanaklı |
| Başlatma maliyeti | yüksek | düşük |
| İleti maliyeti | dizileştirme + süreçler arası kopya | yapısal kopya; büyük veri aktarılabilir |
| Uygun iş | eşzamanlı istek sayısını çoğaltmak | tek bir pahalı hesabı ana yoldan çıkarmak |

Ölçüm toplayıcı için kural şudur: istek işleme kapasitesini artırmak gerekiyorsa
küme, tek bir isteğin içindeki pahalı hesabı ana iş parçacığından çıkarmak
gerekiyorsa işçi iş parçacığı kullanılır. İkisi birlikte de kullanılabilir — her
küme işçisi kendi iş parçacığı havuzunu tutabilir.

İş parçacığı başlatmanın da bir bedeli vardır: yeni bir yürütme ortamı kurulur ve
modüller yeniden değerlendirilir. İstek başına bir iş parçacığı açmak, hesabın
kendisinden pahalıya gelebilir. Yaygın çözüm, önceden açılmış birkaç iş parçacığını
havuzda tutup işleri onlara dağıtmaktır — dosya sistemi çağrılarının kullandığı
havuzun aynı mantığı.

Paylaşılan bellek gerektiğinde `SharedArrayBuffer` üzerinden bir arabellek iki iş
parçacığına da verilebilir. Bu durumda yarış koşulları ortaya çıkar ve erişim
`Atomics` işlemleriyle düzenlenir; tek iş parçacıklı modelin kaldırdığı bütün
eşzamanlılık sorunları bu kapıdan geri girer. Paylaşımı ancak ölçüm sonucu gösterdiği
zaman kullanın.

#### Özet

- Tek süreç tek çekirdek kullanır; girdi/çıktı ağırlıklı işte bu yeterlidir, işlemciye
  bağlı işte değildir.
- Küme, tek bir bağlantı noktasına gelen bağlantıları paylaşan süreçler kurar; dağıtım
  varsayılan olarak döngüseldir, Windows'ta soket tanıtıcısı işçilere devredilir.
- Küme işçileri bellek paylaşmaz; durum tutan bir hizmet kümelenmeden önce durumunu
  süreç dışına taşımalıdır.
- İşçi iş parçacığı aynı süreçte ayrı bir olay döngüsü çalıştırır; pahalı hesap
  oraya taşındığında ana iş parçacığı istek almaya devam eder.
- Seçim ekseni yalıtım ve paylaşımdır: kapasite çoğaltmak için küme, tek isteğin
  içindeki hesabı çıkarmak için iş parçacığı.

#### Sonraki Adım

Yerleşik modüller konusu tamamlandı: dosya, yol, akış, arabellek, olay, HTTP, alt
süreç ve çok çekirdek kullanımı. Bundan sonrası bu parçaları çalışır bir uygulamaya
dönüştürmekle ilgilidir. İlk adım, ölçüm toplayıcıya bir komut satırı yüzü
kazandırmaktır: argümanların ayrıştırılması, boru hattından okuma ve kullanım
metninin doğru akışa yazılması.

## Uygulama Yazımı

Komut satırı araçları, yapılandırma ve gizli değer ayrımı, hata yönetimi stratejisi, yapılandırılmış günlükleme, test, hata ayıklama, zarif kapanma ve paket yayımı.

### Komut Satırı Uygulamaları

Source: https://academia.sh/tr/kurslar/nodejs/komut-satiri-uygulamalari

Seçenek ayrıştırma, girdinin dosyadan ya da boru hattından alınması, terminale bağlılık denetimi, kullanım metninin doğru akışa yazılması ve anlamlı çıkış kodları.

Yerleşik modüller konusu, ölçüm toplayıcının bütün yapı taşlarını kurdu. Hizmet
biçimi ise tek kullanım biçimi değildir: aynı özetleme mantığı, bir boru hattına
takılabilen komut satırı aracı olarak da işe yarar — bir kütük dosyasını hızla
incelemek, bir sunucuyu ayağa kaldırmayı gerektirmemelidir.

Bu ders o aracı yazar. Kabuk Programlama kursundaki süzgeç modeli ölçüt olacak:
argümanları tanımlı biçimde alan, girdisini dosyadan ya da boru hattından okuyan,
verisini birinci akışa, açıklamalarını ikinci akışa yazan ve sonucu çıkış koduyla
bildiren bir araç.

#### Seçenekleri Ayrıştırmak

`process.argv` dizisini elle taramak, üçüncü seçenekten sonra hataya açık hâle gelir:
kısa ve uzun biçim, değer ayracı, birleşik bayraklar, `--` sonrası argümanlar.
`node:util` modülündeki `parseArgs`, bu kuralları tanımlı bir biçimde uygular. Bu fonksiyon
belirli bir sürümden itibaren kararlı sayılır; daha eski bir çalışma zamanı hedefliyorsanız
varlığını sınamanız gerekir.

Ayrıştırıcıya her seçeneğin türü ve varsayılanı bildirilir. `strict` kipinde tanımsız
bir seçenek hata fırlatır; bu, yazım yanlışının sessizce yok sayılmasını engeller.

Aracın çalışması için iki dosya gerekir: HTTP Sunucusu dersinde yazılan `ozetleyici.mjs`
modülü ve Yerleşik Modüller konusundaki on iki satırlık `olcumler.ndjson` dosyası. İkisi de
bu dersin çalışma dizininde bulunmalıdır; yoksa aşağıdaki dosya ve komutlar bunları kurar.

```js
// ozetleyici.mjs
export function satirdanKayit(satir) {
  const kayit = JSON.parse(satir);
  if (typeof kayit.dugum !== 'string' || typeof kayit.metrik !== 'string') {
    throw new TypeError('dugum ve metrik dizgi olmali');
  }
  if (!Number.isFinite(kayit.deger)) {
    throw new TypeError('deger sonlu bir sayi olmali');
  }
  return kayit;
}

export class Ozetleyici {
  #kovalar = new Map();

  ekle({ dugum, metrik, deger }) {
    const anahtar = `${dugum}/${metrik}`;
    const kova = this.#kovalar.get(anahtar) ?? { sayi: 0, toplam: 0, enBuyuk: -Infinity };
    kova.sayi += 1;
    kova.toplam += deger;
    kova.enBuyuk = Math.max(kova.enBuyuk, deger);
    this.#kovalar.set(anahtar, kova);
  }

  ozet() {
    return [...this.#kovalar].map(([anahtar, { sayi, toplam, enBuyuk }]) => ({
      anahtar,
      sayi,
      ortalama: Number((toplam / sayi).toFixed(2)),
      enBuyuk,
    }));
  }
}
```

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

```js
// topla.mjs
import { parseArgs } from 'node:util';
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
import { Ozetleyici, satirdanKayit } from './ozetleyici.mjs';

const KULLANIM = `kullanim: topla.mjs [--dosya YOL] [--metrik AD] [--bicim metin|json]

  --dosya   olcum dosyasi; verilmezse standart girdi okunur
  --metrik  yalnizca bu metrigi ozetle
  --bicim   cikti bicimi: metin veya json (varsayilan: metin)
  --yardim  bu metni yaz ve cik
`;

function seceneklerdenOku(argumanlar) {
  const { values } = parseArgs({
    args: argumanlar,
    options: {
      dosya: { type: 'string' },
      metrik: { type: 'string' },
      bicim: { type: 'string', default: 'metin' },
      yardim: { type: 'boolean', default: false },
    },
    strict: true,
  });
  if (!['metin', 'json'].includes(values.bicim)) {
    throw new TypeError(`--bicim yalnizca metin veya json olabilir, gelen: ${values.bicim}`);
  }
  return values;
}

let secenekler;
try {
  secenekler = seceneklerdenOku(process.argv.slice(2));
} catch (hata) {
  process.stderr.write(`${hata.message}\n\n${KULLANIM}`);
  process.exit(2);
}

if (secenekler.yardim) {
  process.stdout.write(KULLANIM);
  process.exit(0);
}

const girdi = secenekler.dosya ? createReadStream(secenekler.dosya) : process.stdin;
if (!secenekler.dosya && process.stdin.isTTY) {
  process.stderr.write(`girdi yok\n\n${KULLANIM}`);
  process.exit(2);
}

const ozetleyici = new Ozetleyici();
let okunan = 0;
let atlanan = 0;

try {
  for await (const satir of createInterface({ input: girdi, crlfDelay: Infinity })) {
    if (satir.trim() === '') continue;
    okunan += 1;
    let kayit;
    try {
      kayit = satirdanKayit(satir);
    } catch {
      atlanan += 1;
      continue;
    }
    if (secenekler.metrik && kayit.metrik !== secenekler.metrik) continue;
    ozetleyici.ekle(kayit);
  }
} catch (hata) {
  process.stderr.write(`girdi okunamadi: ${hata.code ?? hata.message}\n`);
  process.exitCode = 66;
}

const ozet = ozetleyici.ozet();
if (secenekler.bicim === 'json') {
  process.stdout.write(JSON.stringify(ozet) + '\n');
} else {
  for (const s of ozet) {
    process.stdout.write(`${s.anahtar}\t${s.sayi}\t${s.ortalama}\t${s.enBuyuk}\n`);
  }
}
process.stderr.write(`${okunan} satir okundu, ${atlanan} satir atlandi\n`);
```

Araç, HTTP Sunucusu dersinde yazılan `ozetleyici.mjs` modülünü olduğu gibi kullanır.
Aynı doğrulama ve aynı birikim iki arayüzde de geçerlidir; bu, mantığın sunucudan
ayrı bir modülde tutulmasının karşılığıdır.

```sh
node topla.mjs --dosya olcumler.ndjson
```

```
kenar-01/sicaklik	3	21.87	22.3
kenar-01/nem	2	47.6	48
kenar-02/sicaklik	3	20.23	20.7
kenar-02/nem	1	52.5	52.5
kenar-03/sicaklik	2	24.35	24.6
kenar-03/nem	1	41.3	41.3
12 satir okundu, 0 satir atlandi
```

Son satır standart hataya yazıldı; onu ayırt etmek için akışlardan birini
yönlendirmek yeterlidir.

#### Girdinin İki Kaynağı

Kabuk Programlama kursunda süzgeçler üç öbeğe ayrılmıştı; üçüncü öbek hem dosya adı
hem standart girdi kabul eder. Yukarıdaki araç bu öbektendir: `--dosya` verilmezse
`process.stdin` okunur.

```sh
cat olcumler.ndjson | node topla.mjs --metrik sicaklik --bicim json
```

```
[{"anahtar":"kenar-01/sicaklik","sayi":3,"ortalama":21.87,"enBuyuk":22.3},{"anahtar":"kenar-02/sicaklik","sayi":3,"ortalama":20.23,"enBuyuk":20.7},{"anahtar":"kenar-03/sicaklik","sayi":2,"ortalama":24.35,"enBuyuk":24.6}]
12 satir okundu, 0 satir atlandi
```

Satır satır okuma `node:readline` arayüzüyle yapılıyor. Bu arayüz, akışlar dersinde
elle yazılan satır bölme işini üstlenir ve geri basıncı korur: döngü gövdesi
çalışırken kaynak duraklar. Dosyanın tamamı hiçbir zaman belleğe alınmaz.

`crlfDelay: Infinity` seçeneği, `\r\n` ikilisini tek satır sonu sayar. Farklı
platformlarda üretilmiş dosyalar okunacaksa bu ayar gereklidir.

Aracın çıktısı yine bir boru hattına verilebilir:

```sh
node topla.mjs --dosya olcumler.ndjson 2>/dev/null | sort -t$'\t' -k3 -n -r | head -3
```

```
kenar-02/nem	1	52.5	52.5
kenar-01/nem	2	47.6	48
kenar-03/nem	1	41.3	41.3
```

Sekmeyle ayrılmış sütun biçimi bu bileşim için seçildi. JSON biçimi programla
işlenecek çıktı içindir; sütun biçimi ise Kabuk Programlama kursundaki alan tabanlı
araçlarla doğrudan çalışır.

#### Terminale Bağlılık Denetimi

Standart girdi bir terminale bağlıysa ve kullanıcı dosya adı vermediyse, program
sessizce beklemeye başlar. Kullanıcı için bu bir donma gibi görünür.

`process.stdin.isTTY` alanı, akışın bir terminale bağlı olup olmadığını bildirir.
Boru hattına bağlı bir çalıştırmada değer tanımsızdır:

```js
// tty.mjs
console.log('stdout terminale mi bagli:', Boolean(process.stdout.isTTY));
console.log('stdin  terminale mi bagli:', Boolean(process.stdin.isTTY));
```

```sh
node tty.mjs | cat
```

```
stdout terminale mi bagli: false
stdin  terminale mi bagli: false
```

Aynı betik doğrudan bir uçbirimde çalıştırıldığında iki satır da `true` yazar.

Bu denetim yalnızca donmayı önlemek için değil, çıktı biçimini seçmek için de
kullanılır: çıktı terminaldeyse renk ve hizalama eklenebilir, bir dosyaya veya boruya
gidiyorsa eklenmemelidir. Çünkü renk kaçış dizileri veriye karışır ve bir sonraki
komut onları alan içeriği sanır.

#### Hataları Ayırmak

Aracın üç hata durumu vardır ve üçü ayrı kodlarla bildirilir.

Tanınmayan seçenek, kullanım hatasıdır:

```sh
node topla.mjs --bilinmeyen; echo "cikis kodu: $?"
```

```
Unknown option '--bilinmeyen'

kullanim: topla.mjs [--dosya YOL] [--metrik AD] [--bicim metin|json]

  --dosya   olcum dosyasi; verilmezse standart girdi okunur
  --metrik  yalnizca bu metrigi ozetle
  --bicim   cikti bicimi: metin veya json (varsayilan: metin)
  --yardim  bu metni yaz ve cik
cikis kodu: 2
```

Girdinin okunamaması ayrı bir durumdur ve ayrı bir kod alır:

```sh
node topla.mjs --dosya yok.ndjson; echo "cikis kodu: $?"
```

```
girdi okunamadi: ENOENT
0 satir okundu, 0 satir atlandi
cikis kodu: 66
```

Tek tek bozuk satırlar ise hata değildir; sayılır ve atlanır. Önce içinde bir bozuk satır
bulunan küçük bir dosya hazırlayalım:

```sh
cat > bozuk.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-02","metrik":"nem","deger":50}
SON
```

Sonra aracı bu dosyayla çalıştıralım:

```sh
node topla.mjs --dosya bozuk.ndjson
```

```
kenar-01/nem	1	48	48
kenar-02/nem	1	50	50
3 satir okundu, 1 satir atlandi
```

Bu ayrım bilinçlidir. Toplu ölçüm verisinde tek bir bozuk satır yüzünden bütün
raporu düşürmek, verinin tamamını kullanılamaz kılar; buna karşılık atlanan satır
sayısı sessizce gizlenmemeli, tanı akışına yazılmalıdır.

Kullanım metni **standart hataya** yazılıyor, çünkü bir hatanın parçasıdır. `--yardim`
seçeneğiyle istendiğinde ise standart çıktıya yazılır ve çıkış kodu sıfırdır: orada
kullanım metni istenen sonucun kendisidir.

Dosyanın ilk satırındaki yorumlayıcı bildirimi ile çalıştırma izni verildiğinde araç
doğrudan çağrılabilir. Bildirimi dosyanın başına ekleyip izni verelim:

```sh
{ printf '#!/usr/bin/env node\n'; cat topla.mjs; } > topla.gecici && mv topla.gecici topla.mjs
chmod +x topla.mjs
```

Artık araç adıyla çağrılabilir:

```sh
./topla.mjs --dosya olcumler.ndjson --metrik nem
```

```
kenar-01/nem	2	47.6	48
kenar-02/nem	1	52.5	52.5
kenar-03/nem	1	41.3	41.3
12 satir okundu, 0 satir atlandi
```

#### Özet

- Yerleşik seçenek ayrıştırıcı, seçenek adlarını ve türlerini bildirime dayalı olarak
  alır; katı kip tanımsız seçeneği hata sayar ve yazım yanlışını yakalar.
- Aynı özetleme modülü hem hizmette hem komut satırı aracında kullanılır; mantığın
  arayüzden ayrılmasının karşılığı budur.
- Girdi dosyadan veya standart girdiden alınır; satır satır okuma geri basıncı korur
  ve dosyayı belleğe almaz.
- Terminale bağlılık denetimi hem beklemeyi önler hem de çıktı biçimini seçmeye
  yarar; renk kaçışları boru hattında veriye karışır.
- Kullanım hatası, girdi hatası ve tek tek bozuk kayıtlar ayrı ele alınır; ilk ikisi
  ayrı çıkış kodlarıyla, üçüncüsü sayaçla bildirilir.

#### Sonraki Adım

Araçta ve hizmette hâlâ sabit yazılmış değerler var: dosya adı, bağlantı noktası,
gövde boyutu sınırı. Bunlar çalıştığı ortama göre değişmeli, ama kaynak kodu
değişmemelidir. Sonraki ders yapılandırma yönetimini ele alır: değerlerin nereden
okunacağı, nasıl doğrulanacağı ve gizli değerlerin sıradan ayarlardan neden ayrı
tutulması gerektiği.

### Yapılandırma Yönetimi

Source: https://academia.sh/tr/kurslar/nodejs/yapilandirma-yonetimi

Ayarların kaynak koddan ayrılması, ortam değişkenlerinin doğrulanması, öncelik sırası, gizli değerlerin sıradan ayarlardan ayrılması ve yapılandırmanın değiştirilemez tutulması.

Önceki derste yazılan araçta ve HTTP Sunucusu dersindeki hizmette sabit yazılmış
değerler kaldı: bağlantı noktası 8791, veri dosyasının adı, gövde boyutu sınırı.
Bunlar geliştirme makinesinde doğru, başka bir ortamda yanlıştır.

Bu dersin sorusu şudur: bir ayarın değeri nereden gelmeli ve yanlış geldiğinde ne
olmalı? Yanıtın iki yarısı vardır — kaynakların sırası ve doğrulamanın zamanı.

#### Ayar Kaynak Koddan Ayrılır

Ortama göre değişen her değer koddan çıkarılır. Ölçüt basittir: aynı yapı,
değiştirilmeden başka bir ortamda çalışabilmelidir. Yapıyı yeniden üretmeden ortamı
değiştirebilmek, aynı kodun sınandığı hâliyle çalıştırıldığını güvence altına alır.

Ayarların taşıyıcısı ortam değişkenleridir. Süreç Nesnesi dersinde görüldüğü gibi
bunlar süreç başlatılırken devredilir ve `process.env` üzerinden okunur; değerleri
her zaman dizgidir.

Okuma ve doğrulama tek bir modülde toplanır. Programın geri kalanı `process.env`
nesnesine hiç dokunmaz:

```js
// yapilandirma.mjs
function gerekli(ad) {
  const deger = process.env[ad];
  if (deger === undefined || deger === '') {
    throw new Error(`zorunlu ortam degiskeni eksik: ${ad}`);
  }
  return deger;
}

function tamsayi(ad, varsayilan) {
  const ham = process.env[ad];
  if (ham === undefined) return varsayilan;
  const sayi = Number(ham);
  if (!Number.isInteger(sayi) || sayi <= 0) {
    throw new Error(`${ad} pozitif tam sayi olmali, gelen: ${JSON.stringify(ham)}`);
  }
  return sayi;
}

export function yapilandirmayiOku() {
  return Object.freeze({
    dinlemeAdresi: process.env.OLCUM_ADRES ?? '127.0.0.1',
    dinlemeNoktasi: tamsayi('OLCUM_PORT', 8791),
    veriDizini: process.env.OLCUM_VERI ?? './veri',
    kutukDuzeyi: process.env.OLCUM_KUTUK_DUZEYI ?? 'bilgi',
    yonetimAnahtari: gerekli('OLCUM_YONETIM_ANAHTARI'),
  });
}

// Gizli degerler asla dogrudan yazilmaz
export function yazilabilirGorunum(y) {
  return { ...y, yonetimAnahtari: `***${y.yonetimAnahtari.slice(-2)}` };
}
```

```js
// yap-dene.mjs
import { yapilandirmayiOku, yazilabilirGorunum } from './yapilandirma.mjs';
try {
  console.log(yazilabilirGorunum(yapilandirmayiOku()));
} catch (hata) {
  console.error('yapilandirma hatasi:', hata.message);
  process.exitCode = 78;
}
```

Değişken adlarının ortak bir önek taşıması bilinçlidir: süreç, kendisini başlatan
kabuktan onlarca ilgisiz değişken devralır ve önek, hangilerinin bu programa ait
olduğunu belirsizlikten çıkarır.

#### Doğrulama Başlangıçta Yapılır

Yanlış bir ayarın en kötü ortaya çıkma anı, hizmet saatlerce çalıştıktan sonra ilk
kez o kod yoluna girildiği andır. Yapılandırma **açılışta**, herhangi bir istek
kabul edilmeden önce okunup doğrulanır; geçersizse süreç açılmaz.

```sh
node yap-dene.mjs; echo "cikis kodu: $?"
```

```
yapilandirma hatasi: zorunlu ortam degiskeni eksik: OLCUM_YONETIM_ANAHTARI
cikis kodu: 78
```

```sh
OLCUM_YONETIM_ANAHTARI=abc123XYZ OLCUM_PORT=sekiz node yap-dene.mjs; echo "cikis kodu: $?"
```

```
yapilandirma hatasi: OLCUM_PORT pozitif tam sayi olmali, gelen: "sekiz"
cikis kodu: 78
```

İki hata iletisi de eksik olanı ve beklenen biçimi söylüyor. Yapılandırma hataları
insanlar tarafından okunacak hatalardır; "geçersiz yapılandırma" gibi bir ileti,
düzeltmeyi arayan kişiye hiçbir şey vermez.

Doğru değerlerle açılış:

```sh
OLCUM_YONETIM_ANAHTARI=abc123XYZ OLCUM_PORT=9100 node yap-dene.mjs
```

```
{
  dinlemeAdresi: '127.0.0.1',
  dinlemeNoktasi: 9100,
  veriDizini: './veri',
  kutukDuzeyi: 'bilgi',
  yonetimAnahtari: '***YZ'
}
```

Dizgi olarak gelen `9100` değeri sayıya çevrildi ve tam sayı olduğu doğrulandı.
Dönüşümü tek yerde yapmak, programın geri kalanının tür sorusunu hiç sormamasını
sağlar.

#### Öncelik Sırası

Ayar birden çok kaynaktan gelebilir. Sıra baştan belirlenir ve belgelenir; en yaygın
düzen, dardan genişe doğru şudur:

1. Komut satırı argümanı — tek bir çalıştırmayı etkiler.
2. Ortam değişkeni — o süreci etkiler.
3. Ortam dosyası — bir dizindeki bütün çalıştırmaları etkiler.
4. Koddaki varsayılan — hiçbiri verilmediğinde geçerlidir.

Yukarıdaki sırada üstteki alttakini ezer. Çalışma zamanı, üçüncü basamağı doğrudan
destekler: belirli bir sürümden itibaren bir bayrakla verilen dosyadaki
`AD=deger` satırları `process.env` içine yüklenir.

```sh
printf 'OLCUM_YONETIM_ANAHTARI=dosyadan99\nOLCUM_PORT=9200\n' > .env
node --env-file=.env yap-dene.mjs
```

```
{
  dinlemeAdresi: '127.0.0.1',
  dinlemeNoktasi: 9200,
  veriDizini: './veri',
  kutukDuzeyi: 'bilgi',
  yonetimAnahtari: '***99'
}
```

Ortamda tanımlı bir değişken, dosyadan gelen değeri ezer:

```sh
OLCUM_PORT=9999 node --env-file=.env yap-dene.mjs
```

```
{
  dinlemeAdresi: '127.0.0.1',
  dinlemeNoktasi: 9999,
  veriDizini: './veri',
  kutukDuzeyi: 'bilgi',
  yonetimAnahtari: '***99'
}
```

Ortam dosyası bir geliştirme kolaylığıdır. Sürüm kontrolüne alınmaz; deponun kök
dizininde yalnızca alan adlarını ve açıklamalarını içeren bir örnek dosya tutulur ve
gerçek dosya yok sayılanlar listesine eklenir. Sürüm Kontrolüne Giriş kursunda
tanıtılan yok sayma düzeneği tam olarak bu iş içindir.

#### En Üst Basamağı Gerçekleştirmek

Yukarıdaki listenin ilk maddesi henüz kodda yok: okuyucu yalnızca ortama bakıyor,
komut satırı argümanına bakmıyor. Tek bir çalıştırmayı — bir tanı oturumunu, bir
sınama açılışını — ortam değiştirmeden yönlendirmek için bu basamak gerekir.

Katmanları tek yerde birleştiren okuyucu, her alan için hangi kaynağın kazandığını da
kaydeder:

```js
// yapilandirma-katmanli.mjs
import { parseArgs } from 'node:util';

const TANIM = {
  dinlemeAdresi:  { secenek: 'adres',        degisken: 'OLCUM_ADRES',        tur: 'dizgi',   varsayilan: '127.0.0.1' },
  dinlemeNoktasi: { secenek: 'port',         degisken: 'OLCUM_PORT',         tur: 'tamsayi', varsayilan: 8791 },
  kutukDuzeyi:    { secenek: 'kutuk-duzeyi', degisken: 'OLCUM_KUTUK_DUZEYI', tur: 'dizgi',   varsayilan: 'bilgi' },
};

function donustur(kaynakAdi, ham, tur) {
  if (tur === 'dizgi') return ham;
  const sayi = Number(ham);
  if (!Number.isInteger(sayi) || sayi <= 0) {
    throw new Error(`${kaynakAdi} pozitif tam sayi olmali, gelen: ${JSON.stringify(ham)}`);
  }
  return sayi;
}

// Argumanlar ve ortam disaridan verilir: sinamada gercek surece dokunulmaz
export function yapilandirmayiOku(argumanlar = process.argv.slice(2), ortam = process.env) {
  const { values } = parseArgs({
    args: argumanlar,
    options: Object.fromEntries(Object.values(TANIM).map((t) => [t.secenek, { type: 'string' }])),
    strict: true,
  });

  const deger = {};
  const kaynak = {};
  for (const [alan, t] of Object.entries(TANIM)) {
    if (values[t.secenek] !== undefined) {
      deger[alan] = donustur(`--${t.secenek}`, values[t.secenek], t.tur);
      kaynak[alan] = 'arguman';
    } else if (ortam[t.degisken] !== undefined && ortam[t.degisken] !== '') {
      deger[alan] = donustur(t.degisken, ortam[t.degisken], t.tur);
      kaynak[alan] = 'ortam';
    } else {
      deger[alan] = t.varsayilan;
      kaynak[alan] = 'varsayilan';
    }
  }
  return Object.freeze({ deger: Object.freeze(deger), kaynak: Object.freeze(kaynak) });
}
```

```js
// yap-katman-dene.mjs
import { yapilandirmayiOku } from './yapilandirma-katmanli.mjs';

try {
  const { deger, kaynak } = yapilandirmayiOku();
  for (const alan of Object.keys(deger)) {
    console.log(`${alan.padEnd(15)} ${String(deger[alan]).padEnd(12)} <- ${kaynak[alan]}`);
  }
} catch (hata) {
  console.error('yapilandirma hatasi:', hata.message);
  process.exitCode = 78;
}
```

Hiçbir kaynak verilmediğinde bütün alanlar koddaki varsayılana düşer:

```sh
node yap-katman-dene.mjs
```

```
dinlemeAdresi   127.0.0.1    <- varsayilan
dinlemeNoktasi  8791         <- varsayilan
kutukDuzeyi     bilgi        <- varsayilan
```

Ortam değişkeni varsayılanı ezer:

```sh
OLCUM_PORT=9100 OLCUM_KUTUK_DUZEYI=ayrinti node yap-katman-dene.mjs
```

```
dinlemeAdresi   127.0.0.1    <- varsayilan
dinlemeNoktasi  9100         <- ortam
kutukDuzeyi     ayrinti      <- ortam
```

Argüman da ortamı ezer:

```sh
OLCUM_PORT=9100 node yap-katman-dene.mjs --port 9500
```

```
dinlemeAdresi   127.0.0.1    <- varsayilan
dinlemeNoktasi  9500         <- arguman
kutukDuzeyi     bilgi        <- varsayilan
```

Doğrulama her katmanda aynı kalır; hata iletisi hangi kaynağın adını taşıyorsa
düzeltmenin yapılacağı yer orasıdır:

```sh
node yap-katman-dene.mjs --port sekiz; echo "cikis kodu: $?"
```

```
yapilandirma hatasi: --port pozitif tam sayi olmali, gelen: "sekiz"
cikis kodu: 78
```

Kaynak kaydının tutulması ek bir alan gibi görünür ama tanıda belirleyicidir: bir
ayarın beklenmedik değerde olması, ya yanlış değer verilmesinden ya da beklenen
katmanın hiç okunmamasından gelir. Kaynak alanı ikisini ayırır ve bu bilgi açılış
kütüğüne yazılabilir — gizli olmayan alanlar için.

Okuyucunun argüman dizisini ve ortamı parametre olarak alması, aynı zamanda
sınanabilirlik kararıdır: test, gerçek süreç durumuna dokunmadan bütün katman
birleşimlerini deneyebilir. Bu tasarım Test dersinde yeniden ele alınacak.

#### Gizli Değerler Ayrı Ele Alınır

Bir yönetim anahtarı ile bir bağlantı noktası numarası aynı sınıftan değildir.
İkincisi tanı çıktısına, hata iletisine ve kütüğe yazılabilir; ilki yazılamaz.

Ayrımın uygulanması iki kurala dayanır. Birincisi, yapılandırma nesnesinin doğrudan
yazdırılmamasıdır; yukarıdaki `yazilabilirGorunum` fonksiyonu gizli alanı maskeler ve
yalnızca son iki karakteri bırakır. Bırakılan kısım, yanlış anahtarın yüklendiğini
anlamaya yeter, anahtarı ele vermeye yetmez.

İkincisi, gizli değerin argüman olarak geçirilmemesidir. Komut satırı argümanları
makinedeki süreç listesinde görünür; ortam değişkenleri görünmez. Bu yüzden gizli
değerler yalnızca ortamdan veya bir dosyadan okunur.

Hata nesnelerine de dikkat edilir: bir bağlantı hatası nesnesi, bağlantı dizgisini
alan içinde taşıyabilir. Bu nesnenin olduğu gibi kütüğe yazılması, maskelemeyi
anlamsız kılar. Günlükleme dersinde kütüğe yazılan alanların bilinçli seçilmesinin
nedeni budur.

#### Yapılandırma Değiştirilemezdir

Yapılandırma nesnesi bir kez okunur ve dondurulur. Çalışma sırasında değiştirilebilen
bir ayar, aynı isteğin iki farklı davranış göstermesine yol açar ve tanıyı olanaksız
kılar.

```js
// yap-dondurma.mjs
import { yapilandirmayiOku } from './yapilandirma.mjs';

const y = yapilandirmayiOku();
console.log('dinleme noktasi :', y.dinlemeNoktasi);
console.log('dondurulmus mu  :', Object.isFrozen(y));

try {
  y.dinlemeNoktasi = 1234;      // ES modulleri kati kipte calisir
} catch (hata) {
  console.log('yazma denemesi  :', hata.constructor.name);
}
console.log('deger degismedi :', y.dinlemeNoktasi);
```

```sh
OLCUM_YONETIM_ANAHTARI=abc123XYZ node yap-dondurma.mjs
```

```
dinleme noktasi : 8791
dondurulmus mu  : true
yazma denemesi  : TypeError
deger degismedi : 8791
```

Dondurma, JavaScript'te Nesneler ve Fonksiyonlar kursunda tanıtılan değişmezlik
tekniklerinden biridir. ES modülleri katı kipte çalıştığı için, dondurulmuş nesneye
yazma girişimi sessizce yok sayılmaz; hata fırlatır ve yanlışı yazan kod hemen
görünür.

Dondurma tek düzeylidir: iç içe nesneler ayrıca dondurulmalıdır. Yukarıdaki
yapılandırma yalnızca ilkel değerler taşıdığı için tek çağrı yeterlidir.

#### Özet

- Ortama göre değişen her değer koddan çıkarılır; aynı yapı değiştirilmeden başka bir
  ortamda çalışabilmelidir.
- Yapılandırma tek bir modülde okunur ve doğrulanır; programın geri kalanı ortam
  nesnesine dokunmaz ve tür sorusunu sormaz.
- Doğrulama açılışta yapılır ve başarısızsa süreç açılmaz; hata iletisi eksik alanı
  ve beklenen biçimi söyler.
- Kaynak öncelik sırası baştan belirlenir: argüman, ortam değişkeni, ortam dosyası,
  koddaki varsayılan; her alan için kazanan kaynağın kaydedilmesi tanıyı kısaltır.
- Gizli değerler yazdırılmadan önce maskelenir ve argüman olarak geçirilmez; komut
  satırı argümanları süreç listesinde görünür.

#### Sonraki Adım

Yapılandırma hatası, sürecin açılmasını engelleyen bir hataydı. Çalışma sırasında
ortaya çıkan hatalar aynı davranışı gerektirmez: bir isteğin bozuk gövdesi hizmeti
kapatmamalı, ama bellekte bozulmuş bir durum onu kapatmalıdır. Sonraki ders bu iki
sınıfı ayırır ve hangi hatanın yakalanacağını, hangisinin sürecin sonlanmasına yol
açacağını belirler.

### Hata Yönetimi Stratejisi

Source: https://academia.sh/tr/kurslar/nodejs/hata-yonetimi-stratejisi

İşletimsel hata ile programcı hatasının ayrımı, hata nedenlerinin zincirlenmesi, eşzamansız hataların yakalanamayan biçimleri ve son çare kancalarının doğru kullanımı.

Önceki ders, yapılandırma hatasının sürecin açılmasını engellemesi gerektiğini
söyledi. Çalışma sırasında ortaya çıkan her hata aynı davranışı hak etmez: bir
isteğin bozuk gövdesi hizmeti kapatmamalı, ama bellekte bozulmuş bir durum onu
kapatmalıdır.

Bu dersin sorusu şudur: hangi hata yakalanır, hangisi sürecin sonlanmasına yol açar?
Ayrım tek bir ölçüte dayanır ve stratejinin tamamı ondan çıkar.

#### İki Hata Sınıfı

**İşletimsel hata**, doğru yazılmış bir programın çalışma koşullarında karşılaşmayı
beklediği durumdur: dosya yok, ağ ulaşılamıyor, istemci geçersiz veri gönderdi, disk
doldu. Bunlar programın kusuru değildir; öngörülmüş ve ele alınmıştır.

**Programcı hatası**, kodun kendi sözleşmesini ihlal etmesidir: tanımsız bir değerin
alanına erişmek, zorunlu bir argümanı geçirmeyi unutmak, bir sayı beklenen yere nesne
vermek. Bunlar öngörülmemiştir ve programın o andaki durumu hakkındaki varsayımların
yanlış olduğunu gösterir.

Ayrımın kuralı şudur: **işletimsel hata ele alınır, programcı hatası ele alınmaz.**
İkincisini yakalayıp devam etmek, bozulmuş bir durumla çalışmaya devam etmek
demektir; ne kadar sürdüğü belli olmayan ve tanısı çok daha zor bir arıza üretir.

Ayrımın kodda görünür olması için işletimsel hatalar kendi tipiyle işaretlenir:

```js
// hata-sinifi.mjs
export class IsletimselHata extends Error {
  constructor(ileti, { kod, durum = 500, cause } = {}) {
    super(ileti, { cause });
    this.name = 'IsletimselHata';
    this.kod = kod;
    this.durum = durum;
    this.isletimsel = true;
  }
}

export function isletimselMi(hata) {
  return hata instanceof IsletimselHata && hata.isletimsel === true;
}
```

#### Nedeni Zincirlemek

Alt katmandaki bir hata üst katmana taşınırken çevrilir: üst katman kendi diliyle
konuşan bir hata fırlatır, ama asıl nedeni kaybetmez. `cause` seçeneği bu bağı kurar.

Deneme için Yerleşik Modüller konusundaki on iki satırlık ölçüm dosyası gerekir; çalışma
dizininde yoksa şu komut onu oluşturur:

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

```js
// hata-dene.mjs
import { readFile } from 'node:fs/promises';
import { IsletimselHata, isletimselMi } from './hata-sinifi.mjs';

async function olcumleriOku(yol) {
  try {
    return await readFile(yol, 'utf8');
  } catch (hata) {
    if (hata.code === 'ENOENT') {
      throw new IsletimselHata(`olcum dosyasi yok: ${yol}`, {
        kod: 'OLCUM_DOSYASI_YOK', durum: 503, cause: hata,
      });
    }
    throw hata;
  }
}

for (const yol of ['olcumler.ndjson', 'yok.ndjson']) {
  try {
    const metin = await olcumleriOku(yol);
    console.log(`${yol}: ${metin.trim().split('\n').length} satir`);
  } catch (hata) {
    console.log(`${yol}: isletimsel mi = ${isletimselMi(hata)}, kod = ${hata.kod}, durum = ${hata.durum}`);
    console.log(`  asil neden: ${hata.cause.code}`);
  }
}

// Programci hatasi: sozlesme ihlali, yakalanip yutulmaz
try {
  null.deger;
} catch (hata) {
  console.log(`programci hatasi: isletimsel mi = ${isletimselMi(hata)}, tur = ${hata.constructor.name}`);
}
```

```sh
node hata-dene.mjs
```

```
olcumler.ndjson: 12 satir
yok.ndjson: isletimsel mi = true, kod = OLCUM_DOSYASI_YOK, durum = 503
  asil neden: ENOENT
programci hatasi: isletimsel mi = false, tur = TypeError
```

Üç ayrıntı önemlidir.

Yalnızca tanınan hata kodu çevriliyor; geri kalanı olduğu gibi yukarı fırlatılıyor.
Bütün hataları tek bir tipe çevirmek, bilinmeyen arızaları bilinen bir kategoriye
sokarak gizler.

Çevrilen hata kendi kodunu taşıyor. Çağıran, ileti metnini ayrıştırmadan karar
verebilir; ileti metni değişse de kod sabit kalır.

Asıl neden erişilebilir durumda. Tanı sırasında zincirin en altındaki sistem çağrısı
hatasına ulaşmak, arızayı bulmanın en kısa yoludur.

#### Hizmette Karşılığı

HTTP Sunucusu dersindeki hata yakalayıcı her hatayı 400 olarak bildiriyordu. Ayrım
kurulduktan sonra doğru eşleme yapılabilir:

Sunucu, HTTP Sunucusu dersindeki `ozetleyici.mjs` modülünü içe aktarır. Çalışma dizininde
yoksa aynı modül şöyle yazılır:

```js
// ozetleyici.mjs
export function satirdanKayit(satir) {
  const kayit = JSON.parse(satir);
  if (typeof kayit.dugum !== 'string' || typeof kayit.metrik !== 'string') {
    throw new TypeError('dugum ve metrik dizgi olmali');
  }
  if (!Number.isFinite(kayit.deger)) {
    throw new TypeError('deger sonlu bir sayi olmali');
  }
  return kayit;
}

export class Ozetleyici {
  #kovalar = new Map();

  ekle({ dugum, metrik, deger }) {
    const anahtar = `${dugum}/${metrik}`;
    const kova = this.#kovalar.get(anahtar) ?? { sayi: 0, toplam: 0, enBuyuk: -Infinity };
    kova.sayi += 1;
    kova.toplam += deger;
    kova.enBuyuk = Math.max(kova.enBuyuk, deger);
    this.#kovalar.set(anahtar, kova);
  }

  ozet() {
    return [...this.#kovalar].map(([anahtar, { sayi, toplam, enBuyuk }]) => ({
      anahtar,
      sayi,
      ortalama: Number((toplam / sayi).toFixed(2)),
      enBuyuk,
    }));
  }
}
```

```js
// hata-sunucu.mjs
import { createServer } from 'node:http';
import { IsletimselHata, isletimselMi } from './hata-sinifi.mjs';
import { satirdanKayit } from './ozetleyici.mjs';

function hataYaniti(hata, yanit) {
  if (isletimselMi(hata)) {
    yanit.writeHead(hata.durum, { 'content-type': 'application/json' });
    yanit.end(JSON.stringify({ hata: hata.message, kod: hata.kod }) + '\n');
    return;
  }
  // Programci hatasi: ayrinti disariya verilmez, iceride tam kaydedilir
  process.stderr.write(`programci hatasi: ${hata.stack.split('\n')[0]}\n`);
  yanit.writeHead(500, { 'content-type': 'application/json' });
  yanit.end(JSON.stringify({ hata: 'ic hata', kod: 'IC_HATA' }) + '\n');
}

const sunucu = createServer(async (istek, yanit) => {
  try {
    if (istek.url === '/olcum') {
      const parcalar = [];
      for await (const p of istek) parcalar.push(p);
      const govde = Buffer.concat(parcalar).toString('utf8');
      try {
        satirdanKayit(govde.trim());
      } catch (hata) {
        throw new IsletimselHata('gecersiz olcum kaydi', {
          kod: 'GECERSIZ_KAYIT', durum: 400, cause: hata,
        });
      }
      yanit.writeHead(201, { 'content-type': 'application/json' });
      yanit.end('{"alinan":1}\n');
      return;
    }
    if (istek.url === '/coken') {
      null.deger;                       // bilerek programci hatasi
    }
    throw new IsletimselHata('bulunamadi', { kod: 'YOL_YOK', durum: 404 });
  } catch (hata) {
    hataYaniti(hata, yanit);
  }
});

sunucu.listen(8791, '127.0.0.1', () => console.log('dinleniyor'));
```

Sunucuyu arka planda başlatıp üç istek gönderen bir betik yazalım. Sunucunun standart
hatası ayrı bir dosyaya alınır; böylece programcı hatasının iç ayrıntısının nereye
yazıldığı görünür:

```sh
#!/bin/sh
# istek-denemesi.sh
node hata-sunucu.mjs 2> sunucu-hata.txt &
sunucuPid=$!
sleep 1

curl -s -X POST --data-binary 'bozuk' -w 'durum: %{http_code}\n' http://127.0.0.1:8791/olcum
curl -s -w 'durum: %{http_code}\n' http://127.0.0.1:8791/yok
curl -s -w 'durum: %{http_code}\n' http://127.0.0.1:8791/coken

kill "$sunucuPid"
wait "$sunucuPid" 2>/dev/null
echo "sunucunun standart hatasi:"
cat sunucu-hata.txt
```

```sh
sh istek-denemesi.sh
```

```
dinleniyor
{"hata":"gecersiz olcum kaydi","kod":"GECERSIZ_KAYIT"}
durum: 400
{"hata":"bulunamadi","kod":"YOL_YOK"}
durum: 404
{"hata":"ic hata","kod":"IC_HATA"}
durum: 500
sunucunun standart hatasi:
programci hatasi: TypeError: Cannot read properties of null (reading 'deger')
```

Son çağrıda sunucunun standart hatasına yazılan satır, istemciye verilmeyen iç ayrıntıdır.

İşletimsel hatanın iletisi istemciye veriliyor, programcı hatasınınki verilmiyor. Bu
bir gizleme alışkanlığı değil, iki gerekçeye dayanan bir karardır: yığıt izi ve iç
ileti, dosya yollarını ve kod yapısını dışarıya sızdırır; ayrıca istemcinin
yapabileceği bir şey yoktur — 500 yanıtının tek anlamlı yanıtı yeniden denemek veya
bildirmektir.

#### Eşzamansız Hatanın Yakalanamadığı Yerler

`try`/`catch` yalnızca kendi eşzamanlı gövdesini ve beklenen sözleri kapsar. Bir geri
çağrının içinde fırlatılan hata bu kapsamın dışındadır.

```js
// yakalanmayan.mjs
import { readFile } from 'node:fs/promises';

// try/catch, geri cagriya ertelenen hatayi yakalamaz
try {
  setTimeout(() => { throw new Error('geri cagri icinde firlatildi'); }, 0);
} catch (hata) {
  console.log('bu satir calismaz');
}

// Beklenmeyen reddetme
readFile('yok.ndjson', 'utf8');

console.log('senkron akis bitti');
```

Yakalanmayan hatanın ayrıntısı standart hataya yazılır. Burada onu atıp yalnızca
standart çıktıyı ve çıkış kodunu gösterelim:

```sh
node yakalanmayan.mjs 2>/dev/null; echo "cikis kodu: $?"
```

```
senkron akis bitti
cikis kodu: 1
```

Çıktının ilk satırı `senkron akis bitti` olur; ardından yakalanmayan hata standart
hataya yazılır ve süreç sıfırdan farklı bir kodla sonlanır. `bu satir calismaz` satırı hiç yazılmaz:
`setTimeout` çağrısı hemen döndü, hata ise çok sonra, döngünün zamanlayıcı
aşamasında fırlatıldı — o anda `try` bloğu çoktan bitmişti.

Aynı dosyadaki ikinci sorun daha sinsidir. `readFile` çağrısı beklenmediği için
döndürdüğü sözün reddi hiçbir yerde ele alınmaz. Bu, **işlenmemiş reddetme**dir ve
varsayılan davranışı süreci sonlandırmaktır:

```js
// reddetme.mjs
import { readFile } from 'node:fs/promises';

readFile('yok.ndjson', 'utf8');     // donen soz beklenmiyor, hatasi da islenmiyor
console.log('senkron akis bitti');
```

```sh
node reddetme.mjs > /dev/null; echo "cikis kodu: $?"
```

```
cikis kodu: 1
```

Sürecin sonlanması sert görünür ama doğrudur: sessizce yutulan bir reddetme, verinin
yazıldığı sanılırken yazılmamış olmasına yol açar. Kural şudur: **söz döndüren her
çağrı ya beklenir ya da reddi açıkça ele alınır.**

#### Son Çare Kancaları

İki kanca, yakalanmamış hataları programın eline verir:

```js
// son-care.mjs
process.on('uncaughtException', (hata, koken) => {
  process.stderr.write(`olumcul: ${hata.message} (koken: ${koken})\n`);
  process.exit(1);            // durum bozulmus sayilir: yeniden baslat
});

process.on('unhandledRejection', (neden) => {
  process.stderr.write(`islenmemis reddetme: ${neden.message ?? neden}\n`);
  process.exit(1);
});

setTimeout(() => { throw new Error('gec gelen hata'); }, 0);
console.log('kanca kuruldu');
```

```sh
node son-care.mjs; echo "cikis kodu: $?"
```

```
kanca kuruldu
olumcul: gec gelen hata (koken: uncaughtException)
cikis kodu: 1
```

Bu kancaların tek meşru kullanımı, sürecin ölmeden önce arızayı kaydetmesidir. Kancayı
kurup çalışmaya devam etmek, çalışma zamanının varsayılan davranışını bozar ve
bilinmeyen bir durumda ilerlemeye izin verir.

Kancanın içinde uzun iş yapılmaz. Süreç zaten şüpheli bir durumdadır; kancada
eşzamansız bir kütük yazımı başlatmak, yazının tamamlanacağının güvencesi yoktur.
Kısa ve eşzamanlı bir satır yazıp çıkmak en güvenlisidir.

Süreç öldükten sonra hizmetin ayakta kalması, sürecin kendi sorumluluğu değildir. Bu
işi, süreci yeniden başlatan dış bir denetçi üstlenir; Süreç Yönetimi ve Sağlamlık
dersinde ele alınacak.

#### Özet

- İşletimsel hata öngörülmüş bir çalışma koşuludur ve ele alınır; programcı hatası
  sözleşme ihlalidir ve ele alınmaz.
- İşletimsel hatalar kendi tipleriyle işaretlenir ve kod alanı taşır; çağıran karar
  verirken ileti metnini ayrıştırmaz.
- Hata çevrilirken `cause` ile asıl neden korunur; yalnızca tanınan durumlar çevrilir,
  geri kalanı olduğu gibi yukarı fırlatılır.
- Geri çağrı içinde fırlatılan hata ve beklenmeyen sözün reddi `try`/`catch` kapsamına
  girmez; ikincisinin varsayılan sonucu sürecin sonlanmasıdır.
- Son çare kancaları yalnızca kaydetmek ve çıkmak için kullanılır; kancayı kurup
  devam etmek bilinmeyen bir durumda ilerlemektir.

#### Sonraki Adım

Hataların kaydedilmesinden birkaç kez söz edildi ama nereye ve hangi biçimde
yazılacağı belirlenmedi. Serbest metin bir ileti, arızayı arayan kişinin işine
yaramaz: arama, süzme ve sayma gerektiren bir kayıt biçimi gerekir. Sonraki ders
yapılandırılmış günlüklemeyi kurar ve düzey yönetimini ekler.

### Günlükleme

Source: https://academia.sh/tr/kurslar/nodejs/gunlukleme

Serbest metin yerine yapılandırılmış kayıt, düzey eşiği, isteğe bağlı alanların taşınması, kütüğün standart çıktıya yazılması ve kayıtların araçlarla incelenmesi.

Hata Yönetimi Stratejisi dersinde arızaların kaydedilmesinden birkaç kez söz edildi
ama nereye ve hangi biçimde yazılacağı belirlenmedi. Serbest metin bir satır —
`hata: dosya acilamadi` — arızayı arayan kişiye yetmez: hangi dosya, hangi istek,
hangi süreç, kaç kez?

Bu ders kütük biçimini belirler. Terim seçimi şudur: **kütük (log)**, sürecin
ürettiği kayıt dizisidir; **günlükleme (logging)** bu kayıtları üretme işidir.

#### Yapılandırılmış Kayıt

Kütük satırı iki okuyucuya birden hitap eder: arıza sırasında bakan insan ve
kayıtları süzen, sayan, uyarı üreten program. İkisini birden karşılayan biçim, her
satırın kendi başına ayrıştırılabilir bir kayıt olmasıdır.

Ölçüm dosyasının biçimi burada da işe yarar: satır başına bir JSON nesnesi. Kabuk
Programlama kursundaki satır tabanlı araçlar bu biçimle doğrudan çalışır.

```js
// kutuk.mjs
const DUZEYLER = { ayrinti: 20, bilgi: 30, uyari: 40, hata: 50 };

export function kutukOlustur({
  duzey = 'bilgi',
  hizmet,
  cikti = process.stdout,
  saat = () => new Date().toISOString(),   // sinamada sabitlenebilsin
  sabitAlanlar = {},
} = {}) {
  const esik = DUZEYLER[duzey] ?? DUZEYLER.bilgi;

  function yaz(duzeyAdi, ileti, alanlar = {}) {
    if (DUZEYLER[duzeyAdi] < esik) return;
    cikti.write(JSON.stringify({
      zaman: saat(),
      duzey: duzeyAdi,
      hizmet,
      ileti,
      ...sabitAlanlar,
      ...alanlar,
    }) + '\n');
  }

  const kutuk = {};
  for (const ad of Object.keys(DUZEYLER)) {
    kutuk[ad] = (ileti, alanlar) => yaz(ad, ileti, alanlar);
  }
  // Her isteğe kendi alanlarını taşıyan bir alt kütük
  kutuk.alt = (ekAlanlar) => kutukOlustur({
    duzey, hizmet, cikti, saat,
    sabitAlanlar: { ...sabitAlanlar, ...ekAlanlar },
  });
  return kutuk;
}
```

```js
// kutuk-dene.mjs
import { kutukOlustur } from './kutuk.mjs';

const kutuk = kutukOlustur({
  duzey: process.env.OLCUM_KUTUK_DUZEYI ?? 'bilgi',
  hizmet: 'olcum-toplayici',
  saat: () => '2024-02-07T09:12:44.000Z',      // ornek ciktisi degismesin diye sabit
});

kutuk.ayrinti('yapilandirma okundu', { dosya: '.env' });
kutuk.bilgi('istek alindi', { yol: '/olcum', yontem: 'POST', kayit: 12 });
kutuk.uyari('esik asildi', { dugum: 'kenar-03', deger: 24.6 });
kutuk.hata('olcum dosyasi acilamadi', { kod: 'ENOENT', yol: 'yok.ndjson' });

const istekKutugu = kutuk.alt({ istekKimligi: 'r-0007' });
istekKutugu.bilgi('yanit yazildi', { durum: 201, sure_ms: 3 });
```

```sh
node kutuk-dene.mjs
```

```
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"bilgi","hizmet":"olcum-toplayici","ileti":"istek alindi","yol":"/olcum","yontem":"POST","kayit":12}
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"uyari","hizmet":"olcum-toplayici","ileti":"esik asildi","dugum":"kenar-03","deger":24.6}
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"hata","hizmet":"olcum-toplayici","ileti":"olcum dosyasi acilamadi","kod":"ENOENT","yol":"yok.ndjson"}
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"bilgi","hizmet":"olcum-toplayici","ileti":"yanit yazildi","istekKimligi":"r-0007","durum":201,"sure_ms":3}
```

Zaman damgası örnekte sabitlendi; gerçek çalıştırmada her satır kendi anını taşır.
Saatin dışarıdan verilebilmesi bir kolaylık değil, sınanabilirlik gereğidir: Test
dersinde bu kanca kullanılacak.

İleti metni sabittir, değişken bilgi ayrı alanlara konur. Bu ayrım aramayı olanaklı
kılar: `"ileti":"istek alindi"` araması bütün istekleri bulur, dosya adı iletiye
gömülseydi her satır farklı olurdu.

#### Düzey Eşiği

Dört düzey sayısal ağırlıklarla sıralanır ve eşiğin altındaki kayıtlar hiç
yazılmaz. Eşik yapılandırmadan gelir, yani kütük hacmi kod değiştirmeden ayarlanır.

```sh
OLCUM_KUTUK_DUZEYI=hata node kutuk-dene.mjs
```

```
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"hata","hizmet":"olcum-toplayici","ileti":"olcum dosyasi acilamadi","kod":"ENOENT","yol":"yok.ndjson"}
```

Düzeylerin anlamı sözleşmeyle sabitlenir; anlamsız bir sıra, eşiği işe yaramaz kılar:

- **hata** — işlem tamamlanamadı, birinin bakması gerekiyor.
- **uyari** — işlem tamamlandı ama beklenmeyen bir durum var.
- **bilgi** — hizmetin olağan akışındaki kilometre taşları.
- **ayrinti** — tanı için gereken ayrıntı; olağan çalışmada kapalıdır.

En sık yapılan yanlış, her şeyi `hata` düzeyine yazmaktır. Gerçek arıza, gürültünün
içinde görünmez hâle gelir ve düzey ayrımı anlamını yitirir.

#### İsteğe Bağlı Alanları Taşımak

Eşzamanlı çalışan bir hizmette kütük satırları karışık gelir; bir isteğe ait
satırları toplayabilmek için ortak bir alan gerekir. `alt` yöntemi, verilen alanları
kendisinden üretilen bütün satırlara ekleyen bir kütük döndürür.

Yukarıdaki çıktının son satırında `istekKimligi` alanı bu yolla eklendi. İstek
başında bir kimlik üretilip alt kütük oluşturulduğunda, o isteğin bütün kayıtları
aynı değeri taşır ve tek bir aramayla toplanır.

Kimliğin istemciden gelen bir başlıkta taşınması, isteği birden çok hizmet üzerinden
izlemeyi olanaklı kılar. Gelen kimliğe doğrudan güvenilmez: biçimi doğrulanır ya da
üretilen kendi kimliğiyle birlikte iki alan olarak yazılır.

#### Hatayı Kütüğe Yazmak

Kütüğe yazılan en önemli kayıt hata kaydıdır ve en sık yanlış yazılan da odur. Hata
nesnesi doğrudan bir alana konursa iki sorun çıkar. Birincisi, `Error` nesnesinin
`name`, `message` ve `stack` alanları sayılabilir değildir; dizileştirme onları
atlar ve geriye boş bir nesne kalır. İkincisi, nesnenin tamamını yaymak
(`...hata`) Yapılandırma Yönetimi dersindeki maskelemeyi geçersiz kılar — bir
bağlantı hatası, bağlantı dizgisini kendi alanında taşıyor olabilir.

Doğru yol, yazılacak alanları açıkça seçmektir:

```js
// kutuk-hata.mjs
export function hataAlanlari(hata, { yigitIzi = false } = {}) {
  const alan = { hataAdi: hata.name, hataIletisi: hata.message };
  if (hata.kod !== undefined) alan.hataKodu = hata.kod;
  else if (hata.code !== undefined) alan.hataKodu = hata.code;

  const zincir = [];
  let neden = hata.cause;
  while (neden instanceof Error && zincir.length < 5) {
    zincir.push(neden.code ?? neden.kod ?? neden.name);
    neden = neden.cause;
  }
  if (zincir.length > 0) alan.nedenZinciri = zincir;

  if (yigitIzi && typeof hata.stack === 'string') {
    alan.yigitIzi = hata.stack.split('\n')[0];
  }
  return alan;
}
```

Hata Yönetimi Stratejisi dersinde kurulan neden zinciri burada karşılığını buluyor:
sarmalanmış hatanın altındaki kodlar tek bir dizi alanında toplanıyor.

Deneme, Hata Yönetimi Stratejisi dersindeki hata sınıfına dayanır; çalışma dizininde
yoksa önce onu yazın:

```js
// hata-sinifi.mjs
export class IsletimselHata extends Error {
  constructor(ileti, { kod, durum = 500, cause } = {}) {
    super(ileti, { cause });
    this.name = 'IsletimselHata';
    this.kod = kod;
    this.durum = durum;
    this.isletimsel = true;
  }
}

export function isletimselMi(hata) {
  return hata instanceof IsletimselHata && hata.isletimsel === true;
}
```

```js
// kutuk-hata-dene.mjs
import { readFile } from 'node:fs/promises';
import { kutukOlustur } from './kutuk.mjs';
import { hataAlanlari } from './kutuk-hata.mjs';
import { IsletimselHata } from './hata-sinifi.mjs';

const kutuk = kutukOlustur({
  hizmet: 'olcum-toplayici',
  saat: () => '2024-02-07T09:12:44.000Z',
});

try {
  await readFile('yok.ndjson', 'utf8');
} catch (dusuk) {
  const hata = new IsletimselHata('olcum dosyasi acilamadi', {
    kod: 'OLCUM_DOSYASI_YOK', durum: 503, cause: dusuk,
  });
  kutuk.hata('istek karsilanamadi', { ...hataAlanlari(hata), yol: '/ozet' });
}

kutuk.hata('beklenmeyen tip', { ...hataAlanlari(new TypeError('deger sonlu olmali'), { yigitIzi: true }) });
```

```sh
node kutuk-hata-dene.mjs
```

```
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"hata","hizmet":"olcum-toplayici","ileti":"istek karsilanamadi","hataAdi":"IsletimselHata","hataIletisi":"olcum dosyasi acilamadi","hataKodu":"OLCUM_DOSYASI_YOK","nedenZinciri":["ENOENT"],"yol":"/ozet"}
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"hata","hizmet":"olcum-toplayici","ileti":"beklenmeyen tip","hataAdi":"TypeError","hataIletisi":"deger sonlu olmali","yigitIzi":"TypeError: deger sonlu olmali"}
```

Birinci kayıt zincirin en altındaki `ENOENT` kodunu taşıyor; arızayı arayan kişi,
dosya sistemine kadar inen nedeni tek satırdan okuyor.

Yığıt izi varsayılan olarak yazılmıyor. Gerekçesi iki yönlüdür: iz çok satırlıdır ve
satır başına bir kayıt sözleşmesini bozar; ayrıca dosya yollarını ve dizin yapısını
kütüğe taşır. Örnekte iznin yalnızca ilk satırı alınıyor. Tamamının gerektiği
durumlarda satır sonları kaçırılarak tek bir JSON dizgi değeri olarak yazılır — ham
metin olarak değil.

İşletimsel hata ile programcı hatası kütükte de ayrılır. İşletimsel hata beklenen bir
koşuldur ve `uyari` düzeyi çoğu zaman yeter; programcı hatası her zaman `hata`
düzeyine ve tam iziyle yazılır, çünkü birinin bakması gerekir.

#### Kütük Nereye Yazılır

Kütük **standart çıktıya** yazılır. Süreç kendi dosyasını açmaz, kendi dosya
döndürmesini yapmaz, kendi arşivini tutmaz.

Gerekçe, Kabuk Programlama kursunda kurulan ilkeyle aynıdır: çıktının nereye
gideceğine komut değil, komutu çağıran karar verir. Süreci başlatan denetçi çıktıyı
bir dosyaya, bir toplayıcıya veya bir boru hattına yönlendirir; süreç bunu bilmez.
Böylece aynı yapı, hiçbir değişiklik olmadan farklı işletim düzenlerinde çalışır.

Tanı iletilerinin standart hataya yazılması kuralı burada bir istisnaya uğrar:
yapılandırılmış kütük veriyle karışmaz, çünkü hizmetin "verisi" HTTP yanıtlarıdır,
standart çıktı değildir. Komut satırı araçlarında ise durum tersine döner —
Komut Satırı Uygulamaları dersindeki araç, kütüğünü standart hataya yazar, çünkü
standart çıktı raporun kendisidir.

Yazma davranışının bir ayrıntısı vardır: standart çıktı bir dosyaya bağlıyken yazma
eşzamanlı, bir boruya bağlıyken eşzamansız yürütülebilir. İkinci durumda `process.exit`
ile sert çıkış, yazılmamış satırları düşürebilir — Süreç Nesnesi dersindeki uyarının
somut karşılığı budur.

#### Kayıtları İncelemek

Yapılandırılmış biçimin kazancı, özel bir araç gerektirmemesidir:

```sh
node kutuk-dene.mjs > kutuk.ndjson
grep -o '"duzey":"[a-z]*"' kutuk.ndjson | sort | uniq -c | sort -rn
```

```
      2 "duzey":"bilgi"
      1 "duzey":"uyari"
      1 "duzey":"hata"
```

Alan çıkarmak gerektiğinde kısa bir süzgeç yeter:

```sh
grep '"duzey":"hata"' kutuk.ndjson | node -e "
const satirlar = require('node:fs').readFileSync(0, 'utf8').trim().split('\n');
for (const s of satirlar) { const k = JSON.parse(s); console.log(k.kod, '|', k.ileti); }
"
```

```
ENOENT | olcum dosyasi acilamadi
```

İkinci örnekte `readFileSync(0, 'utf8')` çağrısı, 0 numaralı dosya tanıtıcısını —
yani standart girdiyi — okur. Kabuk Programlama kursunda tanıtılan tanıtıcı
numaraları burada doğrudan kullanılabilir.

Son bir kural: kütüğe kişisel veri ve gizli değer yazılmaz. Yapılandırma Yönetimi
dersinde maskelenen yönetim anahtarı, bir hata nesnesinin alanı olarak kütüğe
sızabilir. Kayda yazılacak alanların açıkça seçilmesi — hata nesnesinin bütününü
serpiştirmek yerine — bu sızıntıyı önler.

#### Özet

- Kütük satırı hem insana hem programa hitap eder; satır başına bir JSON nesnesi
  biçimi iki okuyucuyu birden karşılar.
- İleti metni sabit tutulur, değişken bilgi ayrı alanlara konur; aramayı olanaklı
  kılan budur.
- Düzeyler sayısal ağırlıkla sıralanır ve eşik yapılandırmadan gelir; her şeyi hata
  düzeyine yazmak ayrımı anlamsızlaştırır.
- Alt kütük, verilen alanları kendisinden üretilen bütün satırlara ekler ve bir
  isteğin kayıtlarının toplanmasını sağlar.
- Hata nesnesi doğrudan yazılmaz: alanları açıkça seçilir, neden zinciri kodlara
  indirgenir ve yığıt izi ancak istendiğinde eklenir.
- Kütük standart çıktıya yazılır; dosya, döndürme ve arşiv süreci başlatan denetçinin
  işidir.

#### Sonraki Adım

Ölçüm toplayıcının parçaları çoğaldı: özetleyici, yapılandırma, hata sınıfları,
kütük ve sunucu. Bunların doğru çalıştığı şimdiye kadar elle, çıktıya bakarak
denetlendi. Sonraki ders bu denetimi otomatikleştirir: çalışma zamanının yerleşik
test koşucusu ile birim ve tümleşim testlerini yazar, başarısız bir testin nasıl
göründüğünü ve kapsam raporunun ne söylediğini gösterir.

### Test

Source: https://academia.sh/tr/kurslar/nodejs/test

Yerleşik test koşucusu, birim testinin sınırı, sunucuyu dinlemeden ayırarak tümleşim testi yazma, başarısız testin çıktısı ve kapsam raporunun okunması.

Önceki dört ders ölçüm toplayıcının parçalarını kurdu: özetleyici, yapılandırma,
hata sınıfları, kütük ve sunucu. Bu parçaların doğru çalıştığı şimdiye kadar elle,
çıktıya bakılarak denetlendi.

Elle denetimin iki kusuru vardır: her değişiklikten sonra yinelenmesi gerekir ve
kenar durumlar unutulur. Bu ders denetimi yürütülebilir hâle getirir. Test koşucusu
ve doğrulama modülü çalışma zamanının içinde bulunur; belirli bir sürümden itibaren
kararlı sayılırlar.

#### Birim Testi

Birim testi, tek bir modülü kendi başına, dış dünyaya dokunmadan sınar. Sınanan
birimin bu yalıtımı destekleyecek biçimde yazılmış olması gerekir — HTTP Sunucusu
dersinde özetleme mantığının ayrı modüle alınmasının nedeni buydu.

```js
// ozetleyici.mjs
export function satirdanKayit(satir) {
  const kayit = JSON.parse(satir);
  if (typeof kayit.dugum !== 'string' || typeof kayit.metrik !== 'string') {
    throw new TypeError('dugum ve metrik dizgi olmali');
  }
  if (!Number.isFinite(kayit.deger)) {
    throw new TypeError('deger sonlu bir sayi olmali');
  }
  return kayit;
}

export class Ozetleyici {
  #kovalar = new Map();

  ekle({ dugum, metrik, deger }) {
    const anahtar = `${dugum}/${metrik}`;
    const kova = this.#kovalar.get(anahtar) ?? { sayi: 0, toplam: 0, enBuyuk: -Infinity };
    kova.sayi += 1;
    kova.toplam += deger;
    kova.enBuyuk = Math.max(kova.enBuyuk, deger);
    this.#kovalar.set(anahtar, kova);
  }

  ozet() {
    return [...this.#kovalar].map(([anahtar, { sayi, toplam, enBuyuk }]) => ({
      anahtar,
      sayi,
      ortalama: Number((toplam / sayi).toFixed(2)),
      enBuyuk,
    }));
  }
}
```

```js
// ozetleyici.test.mjs
import { test, describe } from 'node:test';
import assert from 'node:assert/strict';
import { Ozetleyici, satirdanKayit } from './ozetleyici.mjs';

describe('satirdanKayit', () => {
  test('gecerli satiri kayda cevirir', () => {
    const kayit = satirdanKayit('{"dugum":"kenar-01","metrik":"nem","deger":48}');
    assert.deepEqual(kayit, { dugum: 'kenar-01', metrik: 'nem', deger: 48 });
  });

  test('bozuk JSON icin SyntaxError firlatir', () => {
    assert.throws(() => satirdanKayit('bozuk'), SyntaxError);
  });

  test('sayisal olmayan deger icin TypeError firlatir', () => {
    assert.throws(
      () => satirdanKayit('{"dugum":"a","metrik":"b","deger":"yirmi"}'),
      { name: 'TypeError' },
    );
  });
});

describe('Ozetleyici', () => {
  test('ayni anahtardaki olcumleri birlestirir', () => {
    const o = new Ozetleyici();
    o.ekle({ dugum: 'kenar-01', metrik: 'sicaklik', deger: 21.4 });
    o.ekle({ dugum: 'kenar-01', metrik: 'sicaklik', deger: 22.6 });
    assert.deepEqual(o.ozet(), [
      { anahtar: 'kenar-01/sicaklik', sayi: 2, ortalama: 22, enBuyuk: 22.6 },
    ]);
  });

  test('bos ozetleyici bos dizi dondurur', () => {
    assert.deepEqual(new Ozetleyici().ozet(), []);
  });
});
```

```sh
node --test ozetleyici.test.mjs
```

{/* mask: (?<=\()[0-9.]+(?=ms\)) */}
{/* mask: (?<=duration_ms )[0-9.]+ */}
```
▶ satirdanKayit
  ✔ gecerli satiri kayda cevirir (1.11775ms)
  ✔ bozuk JSON icin SyntaxError firlatir (0.155333ms)
  ✔ sayisal olmayan deger icin TypeError firlatir (0.074167ms)
✔ satirdanKayit (2.002583ms)
▶ Ozetleyici
  ✔ ayni anahtardaki olcumleri birlestirir (0.125584ms)
  ✔ bos ozetleyici bos dizi dondurur (0.046625ms)
✔ Ozetleyici (0.240542ms)
ℹ tests 5
ℹ suites 2
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 34.86225
```

Süre değerleri her çalıştırmada değişir; anlamlı olan sayaçlardır.

`node:assert/strict` alt yolu, karşılaştırmaları katı eşitlikle yapan biçimi verir.
JavaScript Temelleri kursunda gevşek eşitliğin tuzakları anlatılmıştı; testte gevşek
karşılaştırma, geçmemesi gereken bir testin geçmesine yol açar.

`assert.throws` çağrısının ikinci argümanı beklenen hatayı tanımlar. Bir yapıcı
verilebilir, bir nesne verilerek alanlar sınanabilir. Yalnızca "bir hata fırlatsın"
demek zayıf bir sınamadır: yanlış nedenle fırlayan hata da testi geçirir.

#### Bağımlılığı Dışarıdan Vermek

Günlükleme dersindeki kütük oluşturucu, saat fonksiyonunu ve çıktı akışını dışarıdan
alıyordu. Bu tasarım tam olarak sınanabilirlik içindir:

Test, Günlükleme dersindeki kütük modülünü içe aktarır; çalışma dizininde yoksa önce
onu yazın:

```js
// kutuk.mjs
const DUZEYLER = { ayrinti: 20, bilgi: 30, uyari: 40, hata: 50 };

export function kutukOlustur({
  duzey = 'bilgi',
  hizmet,
  cikti = process.stdout,
  saat = () => new Date().toISOString(),   // sinamada sabitlenebilsin
  sabitAlanlar = {},
} = {}) {
  const esik = DUZEYLER[duzey] ?? DUZEYLER.bilgi;

  function yaz(duzeyAdi, ileti, alanlar = {}) {
    if (DUZEYLER[duzeyAdi] < esik) return;
    cikti.write(JSON.stringify({
      zaman: saat(),
      duzey: duzeyAdi,
      hizmet,
      ileti,
      ...sabitAlanlar,
      ...alanlar,
    }) + '\n');
  }

  const kutuk = {};
  for (const ad of Object.keys(DUZEYLER)) {
    kutuk[ad] = (ileti, alanlar) => yaz(ad, ileti, alanlar);
  }
  // Her isteğe kendi alanlarını taşıyan bir alt kütük
  kutuk.alt = (ekAlanlar) => kutukOlustur({
    duzey, hizmet, cikti, saat,
    sabitAlanlar: { ...sabitAlanlar, ...ekAlanlar },
  });
  return kutuk;
}
```

```js
// kutuk.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { Writable } from 'node:stream';
import { kutukOlustur } from './kutuk.mjs';

function yakalayanCikti() {
  const satirlar = [];
  const akis = new Writable({
    write(parca, kodlama, bitti) { satirlar.push(parca.toString('utf8')); bitti(); },
  });
  return { akis, satirlar };
}

test('esigin altindaki kayit yazilmaz', () => {
  const { akis, satirlar } = yakalayanCikti();
  const kutuk = kutukOlustur({ duzey: 'uyari', hizmet: 'sinama', cikti: akis, saat: () => 'T' });

  kutuk.bilgi('gorunmemeli');
  kutuk.uyari('gorunmeli');

  assert.equal(satirlar.length, 1);
  assert.deepEqual(JSON.parse(satirlar[0]), {
    zaman: 'T', duzey: 'uyari', hizmet: 'sinama', ileti: 'gorunmeli',
  });
});

test('alt kutuk sabit alanlari her satira ekler', () => {
  const { akis, satirlar } = yakalayanCikti();
  const kutuk = kutukOlustur({ hizmet: 'sinama', cikti: akis, saat: () => 'T' });

  kutuk.alt({ istekKimligi: 'r-1' }).bilgi('ilk', { durum: 200 });

  assert.equal(JSON.parse(satirlar[0]).istekKimligi, 'r-1');
});
```

Sabit saat ve toplayan akış sayesinde test, gerçek zamana ve gerçek çıktıya
dokunmadan tam kaydı doğrular. Modülün içindeki bir değeri sınama sırasında
değiştirmeye gerek kalmaz — bağımlılık zaten dışarıdan verilmektedir.

#### Tümleşim Testi

Tümleşim testi birkaç parçayı birlikte sınar. HTTP hizmeti için sorun şudur:
`hizmet.mjs` içe aktarıldığı anda dinlemeye başlar, bu yüzden test içinden
denetlenemez.

Çözüm, sunucuyu **kurmakla dinlemeye başlamayı ayırmaktır**: modül bir fabrika
fonksiyonu dışa aktarır, dinleme kararını çağıran verir.

```js
// sunucu-olustur.mjs
import { createServer } from 'node:http';
import { Ozetleyici, satirdanKayit } from './ozetleyici.mjs';

export function sunucuOlustur() {
  const ozetleyici = new Ozetleyici();

  return createServer(async (istek, yanit) => {
    const adres = new URL(istek.url, `http://${istek.headers.host}`);

    if (istek.method === 'POST' && adres.pathname === '/olcum') {
      const parcalar = [];
      for await (const parca of istek) parcalar.push(parca);
      const govde = Buffer.concat(parcalar).toString('utf8');
      try {
        let sayac = 0;
        for (const satir of govde.split('\n')) {
          if (satir.trim() === '') continue;
          ozetleyici.ekle(satirdanKayit(satir));
          sayac += 1;
        }
        yanit.writeHead(201, { 'content-type': 'application/json' });
        yanit.end(JSON.stringify({ alinan: sayac }));
      } catch (hata) {
        yanit.writeHead(400, { 'content-type': 'application/json' });
        yanit.end(JSON.stringify({ hata: hata.message }));
      }
      return;
    }

    if (istek.method === 'GET' && adres.pathname === '/ozet') {
      yanit.writeHead(200, { 'content-type': 'application/json' });
      yanit.end(JSON.stringify(ozetleyici.ozet()));
      return;
    }

    yanit.writeHead(404, { 'content-type': 'application/json' });
    yanit.end(JSON.stringify({ hata: 'bulunamadi' }));
  });
}
```

```js
// sunucu.test.mjs
import { test, before, after, describe } from 'node:test';
import assert from 'node:assert/strict';
import { once } from 'node:events';
import { sunucuOlustur } from './sunucu-olustur.mjs';

describe('olcum hizmeti', () => {
  let sunucu;
  let taban;

  before(async () => {
    sunucu = sunucuOlustur();
    sunucu.listen(0, '127.0.0.1');            // 0: bos bir baglanti noktasini cekirdek secer
    await once(sunucu, 'listening');
    taban = `http://127.0.0.1:${sunucu.address().port}`;
  });

  after(async () => {
    sunucu.close();
    await once(sunucu, 'close');
  });

  test('olcum gonderimi 201 doner', async () => {
    const yanit = await fetch(`${taban}/olcum`, {
      method: 'POST',
      body: '{"dugum":"kenar-01","metrik":"nem","deger":48}\n',
    });
    assert.equal(yanit.status, 201);
    assert.deepEqual(await yanit.json(), { alinan: 1 });
  });

  test('ozet gonderilen olcumu yansitir', async () => {
    const yanit = await fetch(`${taban}/ozet`);
    assert.equal(yanit.status, 200);
    assert.deepEqual(await yanit.json(), [
      { anahtar: 'kenar-01/nem', sayi: 1, ortalama: 48, enBuyuk: 48 },
    ]);
  });

  test('bilinmeyen yol 404 doner', async () => {
    const yanit = await fetch(`${taban}/yok`);
    assert.equal(yanit.status, 404);
  });
});
```

```sh
node --test sunucu.test.mjs
```

{/* mask: (?<=\()[0-9.]+(?=ms\)) */}
{/* mask: (?<=duration_ms )[0-9.]+ */}
```
▶ olcum hizmeti
  ✔ olcum gonderimi 201 doner (10.196542ms)
  ✔ ozet gonderilen olcumu yansitir (1.354708ms)
  ✔ bilinmeyen yol 404 doner (0.68975ms)
✔ olcum hizmeti (16.061875ms)
ℹ tests 3
ℹ suites 1
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 73.281666
```

İki ayrıntı bu testi güvenilir kılar.

Bağlantı noktası olarak sıfır verildi: çekirdek boş bir geçici bağlantı noktası
seçer ve gerçek numara `address()` çağrısıyla öğrenilir. Sabit bir numara yazmak,
aynı anda çalışan iki testin çakışmasına yol açar.

`after` kancası sunucuyu kapatıyor ve kapanmayı bekliyor. Kapatılmayan bir sunucu
olay döngüsünü ayakta tutar ve test süreci bitmez.

Test sırasında istek `fetch` ile atıldı — barındıran ortamın web platformundan
devraldığı arayüzlerden biri. İstemci için ayrı bir bağımlılık gerekmedi.

Uçtan uca test bir adım daha ileri gider: yapıyı gerçek bir süreç olarak başlatır,
gerçek yapılandırmayla çalıştırır ve dışarıdan istek atar. Alt Süreçler dersindeki
`spawn` bu iş için kullanılır. Uçtan uca testler yavaş ve kırılgandır; sayıları az
tutulur, kritik yollarla sınırlanır.

#### Başarısız Test ve Kapsam

Bir testin başarısız olduğunda ne yazdığını görmek, çıktıyı okumayı öğrenmenin en
kısa yoludur. Aşağıdaki test bilerek eksik bir beklenti taşıyor:

```js
// basarisiz.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { Ozetleyici } from './ozetleyici.mjs';

test('ozet alanlari', () => {
  const o = new Ozetleyici();
  o.ekle({ dugum: 'kenar-01', metrik: 'nem', deger: 48 });
  assert.deepEqual(o.ozet(), [{ anahtar: 'kenar-01/nem', sayi: 1, ortalama: 48 }]);
});
```

```sh
node --test basarisiz.test.mjs > rapor.txt; kod=$?
head -n 25 rapor.txt
echo "cikis kodu: $kod"
```

{/* mask: (?<=\()[0-9.]+(?=ms\)) */}
{/* mask: (?<=duration_ms )[0-9.]+ */}
```
✖ ozet alanlari (0.978292ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 33.174375

✖ failing tests:

test at basarisiz.test.mjs:6:1
✖ ozet alanlari (0.978292ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
  + actual - expected
  
    [
      {
        anahtar: 'kenar-01/nem',
  +     enBuyuk: 48,
        ortalama: 48,
        sayi: 1
      }
    ]
cikis kodu: 1
```

Rapor `head` ile ilk 25 satıra kısaltıldı; tam çıktı bunun ardından hata nesnesinin
alanlarını ve çağrı izini de yazar. Fark raporu, gerçek değerde bulunup beklenende olmayan alanı `+` ile işaretliyor.
Sıfırdan farklı çıkış kodu, testin bir otomasyon adımından çağrıldığında
başarısızlığı bildirmesini sağlar.

Hangi satırların hiç çalışmadığını görmek için kapsam ölçümü açılır:

```sh
node --test --experimental-test-coverage ozetleyici.test.mjs
```

{/* mask: (?<=\()[0-9.]+(?=ms\)) */}
{/* mask: (?<=duration_ms )[0-9.]+ */}
```
▶ satirdanKayit
  ✔ gecerli satiri kayda cevirir (1.11775ms)
  ✔ bozuk JSON icin SyntaxError firlatir (0.155333ms)
  ✔ sayisal olmayan deger icin TypeError firlatir (0.074167ms)
✔ satirdanKayit (2.002583ms)
▶ Ozetleyici
  ✔ ayni anahtardaki olcumleri birlestirir (0.125584ms)
  ✔ bos ozetleyici bos dizi dondurur (0.046625ms)
✔ Ozetleyici (0.240542ms)
ℹ tests 5
ℹ suites 2
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 34.86225
ℹ start of coverage report
ℹ ---------------------------------------------------------------
ℹ file           | line % | branch % | funcs % | uncovered lines
ℹ ---------------------------------------------------------------
ℹ ozetleyici.mjs |  93.94 |    90.91 |  100.00 | 5-6
ℹ ---------------------------------------------------------------
ℹ all files      |  93.94 |    90.91 |  100.00 |
ℹ ---------------------------------------------------------------
ℹ end of coverage report
```

Kapsam raporu, test sonuçlarının ardından gelir. Rapor, 5 ve 6 numaralı satırların hiç çalışmadığını söylüyor: bunlar `dugum` alanı
dizgi olmadığında çalışan dal. Kapsam yüzdesi bir hedef değil bir işarettir — hangi
davranışın hiç sınanmadığını gösterir, sınanan davranışların doğru sınandığını
göstermez.

#### Özet

- Birim testi tek modülü yalıtımda sınar; bu yalıtım, mantığın arayüzden ayrı bir
  modülde tutulmasını gerektirir.
- Bağımlılıklar (saat, çıktı akışı) dışarıdan verildiğinde test gerçek zamana ve
  gerçek çıktıya dokunmadan tam kaydı doğrular.
- Sunucu kurmak ile dinlemeye başlamak ayrıldığında hizmet tümleşim testine açılır;
  test sıfır numaralı bağlantı noktası ister ve sonunda sunucuyu kapatır.
- Katı doğrulama biçimi gevşek eşitliğin tuzaklarını dışarıda bırakır; fırlatma
  sınamalarında beklenen hata tipi de belirtilir.
- Kapsam raporu hangi davranışın hiç sınanmadığını gösterir; sınananların doğru
  sınandığını göstermez.

#### Sonraki Adım

Testler, beklenen davranışın korunduğunu söyler; beklenmeyen bir davranışın nedenini
söylemez. Süreç yavaşladığında, belleği büyüdüğünde veya beklenmedik bir yerde
takıldığında başka araçlar gerekir. Sonraki ders çalışma zamanının inceleme
arayüzünü, işlemci profilini ve öbek anlık görüntüsünü ele alır.

### Hata Ayıklama ve Profilleme

Source: https://academia.sh/tr/kurslar/nodejs/hata-ayiklama-ve-profilleme

İnceleyici protokolünün açılması, bellek kullanımının alanları, öbek anlık görüntüsü, işlemci profilinin okunması ve kod içi süre ölçümü.

Testler, beklenen davranışın korunduğunu söyler. Süreç yavaşladığında, belleği
büyüdüğünde veya beklenmedik bir yerde takıldığında testler sessiz kalır: bunlar
doğruluk sorunu değil, davranış sorunudur.

Bu ders o sorunları görünür kılan araçları ele alır. Hepsi çalışma zamanının
içindedir; ek bir bileşen gerekmez.

#### İnceleyici Arayüzü

Çalışma zamanı, bir hata ayıklayıcının bağlanabileceği bir arayüz açabilir. Arayüz
bir bağlantı noktası dinler ve üzerinden kesme noktası koyma, adımlama, değişken
inceleme ve profil alma komutları geçirilir.

```sh
node --inspect=127.0.0.1:9229 -e "setTimeout(()=>{},200)"
```

{/* mask: (?<=ws://127[.]0[.]0[.]1:9229/)[0-9a-f-]+ */}
```
Debugger listening on ws://127.0.0.1:9229/0dd16a67-741b-482d-86db-82b41709dc92
For help, see: https://nodejs.org/learn/getting-started/debugging
```

Adresin sonundaki kimlik her çalıştırmada değişir; oturumu tanımlar ve rastgele
bağlanmayı zorlaştırır. Bağlantı noktası numarası da isteğe göre değiştirilir.

Adresin başındaki `127.0.0.1` bölümü ihmal edilmemelidir. Bu arayüz, sürecin
belleğine tam erişim verir ve keyfi kod çalıştırabilir; ağa açık bir arayüzde
dinlemesi, sürecin denetimini dışarıya bırakmak demektir. Bir sunucuda tanı almak
gerektiğinde arayüz yerel adreste açılır ve bağlantı, Linux Ağ Yönetimi ve Sorun
Giderme kursunda tanıtılan güvenli kabuk üzerinden tünellenir.

`--inspect-brk` bayrağı, ilk satırı çalıştırmadan önce durur; açılış sırasında oluşan
sorunları incelemenin yoludur. Zaten çalışan bir sürece sonradan da bağlanılabilir:
`SIGUSR1` sinyali alan bir süreç inceleyici arayüzünü açar.

#### Bellek Kullanımını Okumak

`process.memoryUsage()` çağrısı birkaç ayrı sayı döndürür ve hangisine bakıldığı
önemlidir:

```sh
node -e "console.log(process.memoryUsage())"
```

{/* mask: (?<=rss: )[0-9]+ */}
{/* mask: (?<=heapTotal: )[0-9]+ */}
{/* mask: (?<=heapUsed: )[0-9]+ */}
{/* mask: (?<=external: )[0-9]+ */}
```
{
  rss: 45137920,
  heapTotal: 5783552,
  heapUsed: 3768872,
  external: 1582649,
  arrayBuffers: 137503
}
```

Sayılar bayt cinsindendir ve her çalıştırmada değişir; büyüklük sıraları anlamlıdır.

- `rss`, sürecin fiziksel bellekte tuttuğu toplam alandır; çalışma zamanının kendi
  kodunu ve yığıtı da içerir.
- `heapTotal`, JavaScript nesneleri için ayrılmış öbek alanıdır.
- `heapUsed`, o öbeğin gerçekten kullanılan bölümüdür. Sızıntı aranırken bakılan sayı
  budur.
- `external`, V8'e bağlı C++ nesnelerinin bellek kullanımıdır. `arrayBuffers` bunun
  bir alt kümesidir; `ArrayBuffer` ve arabelleklere ayrılan belleği sayar.

Bilerek büyüyen bir diziyle farkı görelim:

```js
// sizinti.mjs
import { mkdirSync } from 'node:fs';
import v8 from 'node:v8';

mkdirSync('profil', { recursive: true });

const gecmis = [];

function olcumEkle(kayit) {
  gecmis.push(kayit);            // hicbir zaman bosaltilmiyor
}

function obekMB() {
  return (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(0);
}

console.log('baslangicta kullanilan obek (MB):', obekMB());
for (let i = 0; i < 500000; i += 1) {
  olcumEkle({ dugum: `kenar-${i % 3}`, metrik: 'sicaklik', deger: i % 40 });
}
console.log('500 bin kayittan sonra (MB)     :', obekMB());

const yol = v8.writeHeapSnapshot('profil/olcum.heapsnapshot');
console.log('anlik goruntu                   :', yol.split('/').pop());
```

```sh
node sizinti.mjs
```

```
baslangicta kullanilan obek (MB): 4
500 bin kayittan sonra (MB)     : 48
anlik goruntu                   : olcum.heapsnapshot
```

Megabayt değerleri çalışma zamanı sürümüne ve çöp toplayıcının o andaki durumuna göre
birkaç birim oynar; belirleyici olan artışın büyüklüğüdür.

Bu, ölçüm toplayıcının gerçek bir riskidir: gelen her kaydı bir diziye eklemek,
belleği girdi hacmiyle doğru orantılı büyütür. Özetleyicinin kova haritası bu yüzden
kayıtları saklamaz, yalnızca sayaç ve toplam tutar — bellek, ayrı anahtar sayısıyla
sınırlıdır.

#### Öbek Anlık Görüntüsü

`writeHeapSnapshot`, o andaki bütün nesneleri ve aralarındaki başvuruları bir dosyaya
yazar. Dosya bir çizgedir: her düğüm bir nesne, her kenar bir başvuru — Veri
Yapıları kursundaki çizge gösterimlerinin doğrudan uygulaması.

Anlık görüntü tek başına az şey söyler. Yöntem, **iki görüntüyü karşılaştırmaktır**:
biri yükten önce, biri sonra alınır ve arada hangi nesne türünün sayısının arttığına
bakılır. Sürekli artan ve hiç azalmayan bir tür, sızıntının kaynağına götürür.

Görüntü alma işlemi süreci durdurur ve büyük bir dosya üretir; yukarıdaki örnekte
dosya onlarca megabayttır. Üretim ortamında alınacaksa, isteklerin geciktirilmesi
göze alınarak ve disk alanı denetlenerek yapılır.

Aynı sinyal düzeneği burada da kullanılabilir: süreç, önceden belirlenmiş bir sinyal
aldığında anlık görüntü alacak biçimde yazılabilir. Böylece sorun ortaya çıktığı anda,
süreci yeniden başlatmadan görüntü alınır.

#### İşlemci Profili

İşlemci profili, sürecin zamanını hangi fonksiyonda geçirdiğini örnekleyerek ölçer.
Belirli aralıklarla çağrı yığıtının anlık görüntüsü alınır; bir fonksiyon örneklerin
çoğunda görünüyorsa zamanın çoğunu orada geçiriyordur.

Profil bir bayrakla açılır ve süreç bittiğinde dosyaya yazılır. Küme ve İşçi İş
Parçacıkları dersindeki pahalı hesabı profilleyelim; ana iş parçacığında çalışan
sürümü çalışma dizininde yoksa önce yazın:

```js
// isci-yok.mjs
const YINELEME = 3e8;

function agirToplam(yineleme) {
  let toplam = 0;
  for (let i = 0; i < yineleme; i += 1) toplam += Math.sqrt(i) % 1;
  return toplam;
}

setTimeout(() => console.log('3 zamanlayici ancak simdi calisti'), 0);
console.log('1 hesap basliyor');
console.log('2 sonuc:', Number(agirToplam(YINELEME).toFixed(3)));
```

```sh
node --cpu-prof --cpu-prof-dir=cpu-profil isci-yok.mjs
```

Üretilen dosya JSON biçimindedir ve doğrudan okunabilir:

```js
// profil-oku.mjs
import { readdirSync, readFileSync } from 'node:fs';
import path from 'node:path';

const dizin = process.argv[2] ?? 'cpu-profil';
const dosya = readdirSync(dizin).find((a) => a.endsWith('.cpuprofile'));
const profil = JSON.parse(readFileSync(path.join(dizin, dosya), 'utf8'));

const cerceveler = new Map(profil.nodes.map((d) => [d.id, d.callFrame]));
const sayaclar = new Map();
for (const id of profil.samples) sayaclar.set(id, (sayaclar.get(id) ?? 0) + 1);

const toplam = profil.samples.length;
console.log(`dosya: ${dosya.replace(/\d{8}\.\d{6}\.\d+/, 'TARIH.SAAT.PID')}`);
console.log(`ornek sayisi: ${toplam}`);
for (const [id, say] of [...sayaclar].sort((a, b) => b[1] - a[1]).slice(0, 1)) {
  const c = cerceveler.get(id);
  const ad = c.functionName || '(anonim)';
  const yer = c.url ? `${path.basename(c.url)}:${c.lineNumber + 1}` : '(ic)';
  console.log(`${((say / toplam) * 100).toFixed(0).padStart(3)}%  ${ad.padEnd(14)} ${yer}`);
}
```

```sh
node profil-oku.mjs cpu-profil
```

{/* mask: (?<=ornek sayisi: )[0-9]+ */}
{/* mask: ^ *(9[0-9]|100)(?=%  agirToplam) */}
```
dosya: CPU.TARIH.SAAT.PID.0.001.cpuprofile
ornek sayisi: 1829
100%  agirToplam     isci-yok.mjs:4
```

Dosya adı tarih, saat ve süreç kimliğini taşır; örnekte okunabilirlik için
maskelendi. Betik yalnızca en çok örnek alan fonksiyonu yazar; örnek sayısı
çalıştırmadan çalıştırmaya değişir, yüzde de bir iki birim oynayabilir. Sabit olan
sonuçtur: zamanın tamamına yakını tek bir fonksiyonda geçmiş.

Profil okumanın iki kuralı vardır. Birincisi, yüzdesi düşük çok sayıda satır
görüyorsanız darboğaz tek bir yerde değildir ve tek bir eniyileme sonucu
değiştirmeyecektir. İkincisi, örnekleme yalnızca çalışan işlemciyi görür: girdi/çıktı
beklerken geçen süre profilde görünmez. Bir isteğin yavaşlığı beklemeden geliyorsa
işlemci profili boş çıkar ve süreyi başka türlü ölçmek gerekir.

#### Kod İçi Süre Ölçümü

Beklemeyi de kapsayan ölçüm için `node:perf_hooks` modülü kullanılır. İşaretler
konur, aralar ölçülür ve ölçümler bir gözlemciyle toplanır.

Deneme için Komut Satırı Uygulamaları dersindeki özetleyici modülü ve ölçüm dosyası
gerekir; çalışma dizininde yoksa önce bunları yazın:

```js
// ozetleyici.mjs
export function satirdanKayit(satir) {
  const kayit = JSON.parse(satir);
  if (typeof kayit.dugum !== 'string' || typeof kayit.metrik !== 'string') {
    throw new TypeError('dugum ve metrik dizgi olmali');
  }
  if (!Number.isFinite(kayit.deger)) {
    throw new TypeError('deger sonlu bir sayi olmali');
  }
  return kayit;
}

export class Ozetleyici {
  #kovalar = new Map();

  ekle({ dugum, metrik, deger }) {
    const anahtar = `${dugum}/${metrik}`;
    const kova = this.#kovalar.get(anahtar) ?? { sayi: 0, toplam: 0, enBuyuk: -Infinity };
    kova.sayi += 1;
    kova.toplam += deger;
    kova.enBuyuk = Math.max(kova.enBuyuk, deger);
    this.#kovalar.set(anahtar, kova);
  }

  ozet() {
    return [...this.#kovalar].map(([anahtar, { sayi, toplam, enBuyuk }]) => ({
      anahtar,
      sayi,
      ortalama: Number((toplam / sayi).toFixed(2)),
      enBuyuk,
    }));
  }
}
```

```sh
cat > olcumler.ndjson <<'SON'
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.4}
{"zaman":"2024-02-07T09:12:44Z","dugum":"kenar-01","metrik":"nem","deger":48.0}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"sicaklik","deger":19.8}
{"zaman":"2024-02-07T09:13:02Z","dugum":"kenar-02","metrik":"nem","deger":52.5}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-01","metrik":"sicaklik","deger":21.9}
{"zaman":"2024-02-07T09:14:19Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.1}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.2}
{"zaman":"2024-02-07T09:15:30Z","dugum":"kenar-03","metrik":"nem","deger":41.3}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-01","metrik":"nem","deger":47.2}
{"zaman":"2024-02-07T09:16:04Z","dugum":"kenar-03","metrik":"sicaklik","deger":24.6}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-02","metrik":"sicaklik","deger":20.7}
{"zaman":"2024-02-07T09:17:22Z","dugum":"kenar-01","metrik":"sicaklik","deger":22.3}
SON
```

```js
// olcum-suresi.mjs
import { performance, PerformanceObserver } from 'node:perf_hooks';
import { readFile } from 'node:fs/promises';
import { Ozetleyici, satirdanKayit } from './ozetleyici.mjs';

const gozlemci = new PerformanceObserver((liste) => {
  for (const kayit of liste.getEntries()) {
    console.log(`${kayit.name.padEnd(12)} ${kayit.duration.toFixed(2)} ms`);
  }
});
gozlemci.observe({ entryTypes: ['measure'] });

performance.mark('okuma-basi');
const metin = await readFile('olcumler.ndjson', 'utf8');
performance.mark('okuma-sonu');

const o = new Ozetleyici();
for (const satir of metin.split('\n')) {
  if (satir.trim() === '') continue;
  o.ekle(satirdanKayit(satir));
}
performance.mark('ozet-sonu');

const okuma = performance.measure('okuma', 'okuma-basi', 'okuma-sonu');
const ozetleme = performance.measure('ozetleme', 'okuma-sonu', 'ozet-sonu');
console.log('okuma > ozetleme:', okuma.duration > ozetleme.duration);
```

```sh
node olcum-suresi.mjs
```

{/* mask: (?<=[ ])[0-9]+[.][0-9]+(?=[ ]ms) */}
```
okuma > ozetleme: true
okuma        0.82 ms
ozetleme     0.13 ms
```

Süreler her çalıştırmada değişir; ölçüm bir kez alınıp karar verilmez, tekrarlanır ve
dağılımına bakılır. Anlamlı olan iki aşamanın karşılaştırmasıdır; betik bunu her
çalıştırmada kendisi ölçüp ilk satıra yazar. Bu küçük dosyada okuma, özetlemeden
uzun sürer.

Ölçüm sonuçları Günlükleme dersindeki kütük kaydına alan olarak eklenebilir. Süre
alanı taşıyan kayıtlar biriktiğinde, yavaşlamanın hangi aşamadan geldiği profil almaya
gerek kalmadan görülür — bu yüzden istek kütüklerine süre alanı yazmak yaygın bir
alışkanlıktır.

#### Özet

- İnceleyici arayüzü sürecin belleğine tam erişim verir; yerel adreste açılır ve
  uzaktan erişim tünellenerek yapılır.
- Bellek alanları ayrı anlamlar taşır; sızıntı ararken kullanılan öbek alanına,
  ikili veri için öbek dışı alanlara bakılır.
- Öbek anlık görüntüsü nesneleri ve başvuruları bir çizge olarak yazar; tanı, iki
  görüntünün karşılaştırılmasıyla yapılır.
- İşlemci profili örnekleme ile çalışır ve yalnızca çalışan işlemciyi görür; bekleme
  süresi profilde görünmez.
- İşaret ve ara ölçümü beklemeyi de kapsar; süre alanının kütüğe yazılması, profil
  almadan yavaşlamanın kaynağını gösterir.

#### Sonraki Adım

Buraya kadar süreç hep elle başlatıldı ve uçbirim kapatılarak durduruldu. Üretimde
süreci bir denetçi başlatır, çöktüğünde yeniden açar ve kapanması gerektiğinde bir
sinyal gönderir. Sonraki ders bu sözleşmenin süreç tarafını kurar: sinyalleri
karşılamak, süren istekleri tamamlayıp yeni bağlantı almamak ve hizmetin durumunu
denetçiye bildirmek.

### Süreç Yönetimi ve Sağlamlık

Source: https://academia.sh/tr/kurslar/nodejs/surec-yonetimi-ve-saglamlik

Sinyallerin karşılanması, zarif kapanmanın aşamaları, canlılık ile hazırlık uçlarının ayrımı, yeniden başlatma sözleşmesi ve kapanma süresinin sınırlanması.

Buraya kadar süreç hep elle başlatıldı ve uçbirim kapatılarak durduruldu. Üretimde
bu iş bir denetçiye aittir: süreci başlatır, çöktüğünde yeniden açar, güncelleme
sırasında durdurur.

Denetçi ile süreç arasında bir sözleşme vardır ve bu dersin konusu sözleşmenin süreç
tarafıdır. Üç madde: kapanma isteğini karşılamak, süren işi bitirip yeni iş almamak,
kendi durumunu bildirmek.

#### Kapanma İsteği Bir Sinyaldir

Kabuk Programlama kursunda sinyaller ve tuzaklar ele alınmıştı; aynı düzenek burada
süreç nesnesinin olayları olarak görünür.

Denetçinin kapanma isteği `SIGTERM` sinyalidir. Uçbirimde kesme tuşuna basılması
`SIGINT` üretir. İkisinin de varsayılan davranışı süreci hemen sonlandırmaktır; bir
dinleyici takıldığında varsayılan davranış devre dışı kalır ve karar programa geçer.

Yakalanamayan bir sinyal de vardır. Denetçi, kibar istekten sonra tanıdığı süre
içinde süreç kapanmazsa onu kullanır ve süreç hiçbir temizlik yapamadan sonlanır.
Bu yüzden kapanışın kendi süre sınırı olmalıdır: denetçinin sınırından önce
bitirmeyen bir kapanış, hiç yapılmamış sayılır.

#### Zarif Kapanmanın Aşamaları

**Zarif kapanma (graceful shutdown)**, sürecin yeni iş almayı bırakıp elindeki işi
bitirerek sonlanmasıdır. Dört aşaması vardır ve sıraları önemlidir.

```js
// saglam-sunucu.mjs
import { createServer } from 'node:http';

const CIKIS_BEKLEME_MS = 500;    // denetciye "artik hazir degilim" demek icin
const KAPANMA_SINIRI_MS = 3000;  // suren istekler icin verilen en cok sure

let hazir = false;
let kapaniyor = false;

const sunucu = createServer((istek, yanit) => {
  if (istek.url === '/canli') {                 // surec ayakta mi
    yanit.writeHead(200).end('canli\n');
    return;
  }
  if (istek.url === '/hazir') {                 // istek almaya hazir mi
    const kod = hazir && !kapaniyor ? 200 : 503;
    yanit.writeHead(kod).end(kod === 200 ? 'hazir\n' : 'hazir degil\n');
    return;
  }
  if (istek.url === '/yavas') {                 // suren bir istegi taklit eder
    setTimeout(() => yanit.writeHead(200).end('bitti\n'), 800);
    return;
  }
  yanit.writeHead(404).end('bulunamadi\n');
});

sunucu.listen(8791, '127.0.0.1', () => {
  hazir = true;
  console.log(`dinleniyor, surec kimligi ${process.pid}`);
});

function kapan(sinyal) {
  if (kapaniyor) return;
  kapaniyor = true;
  console.log(`${sinyal} alindi; hazirlik ucu artik 503 donuyor`);

  setTimeout(() => {
    console.log('yeni baglanti kabul edilmiyor');

    const zorlayici = setTimeout(() => {
      console.log('sure doldu; kalan baglantilar kesiliyor');
      sunucu.closeAllConnections();
    }, KAPANMA_SINIRI_MS);
    zorlayici.unref();                           // tek basina sureci ayakta tutmasin

    sunucu.close(() => {
      clearTimeout(zorlayici);
      console.log('tum istekler tamamlandi; cikiliyor');
    });
    sunucu.closeIdleConnections();
  }, CIKIS_BEKLEME_MS).unref();
}

process.on('SIGTERM', () => kapan('SIGTERM'));
process.on('SIGINT', () => kapan('SIGINT'));
```

Denemeyi bir betikle yürütelim:

```sh
#!/bin/sh
# kapanma-denemesi.sh
node saglam-sunucu.mjs &
sunucuPid=$!
sleep 1

printf 'kapanmadan once /hazir  -> '; curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8791/hazir

# Suren bir istek baslat, yanit beklerken sinyali gonder
curl -s http://127.0.0.1:8791/yavas > yavas-yanit.txt &
istekPid=$!
sleep 0.1
kill -TERM "$sunucuPid"

sleep 0.2
printf 'kapanma sirasinda /hazir -> '; curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8791/hazir

wait "$istekPid"
echo "suren istegin yaniti: $(cat yavas-yanit.txt)"
wait "$sunucuPid"
echo "surecin cikis kodu: $?"
```

```sh
sh kapanma-denemesi.sh
```

{/* mask: (?<=surec kimligi )[0-9]+ */}
```
dinleniyor, surec kimligi 18312
kapanmadan once /hazir  -> 200
SIGTERM alindi; hazirlik ucu artik 503 donuyor
kapanma sirasinda /hazir -> 503
yeni baglanti kabul edilmiyor
tum istekler tamamlandi; cikiliyor
suren istegin yaniti: bitti
surecin cikis kodu: 0
```

Süreç kimliği her çalıştırmada değişir. Çıktının anlattığı sıra şudur.

**Birinci aşama: hazır olmadığını bildir.** Sinyal alınır alınmaz hazırlık ucu 503
dönmeye başlar. Sürecin önünde bir yük dağıtıcı varsa, onu listeden çıkarması için
bir pencere gerekir; bu pencere olmadan dağıtıcı, kapanmakta olan sürece istek
göndermeye devam eder ve o istekler düşer.

**İkinci aşama: yeni bağlantı alma.** Bekleme süresi dolduğunda `close` çağrılır.
Bu çağrı dinleyen soketi kapatır ama var olan bağlantıları koparmaz. Boşta bekleyen
kalıcı bağlantılar ayrıca kapatılır; yoksa istemci hiçbir istek göndermese de
bağlantıyı açık tutar ve kapanış uzar.

**Üçüncü aşama: süren işi bitir.** `close` geri çağrısı, son istek de tamamlandığında
çalışır. Yukarıdaki çıktıda süren isteğin `bitti` yanıtını aldığı görülüyor — sinyal,
o istek işlenirken gönderilmişti.

**Dördüncü aşama: süre sınırı.** Bir istemci bağlantıyı hiç bırakmazsa kapanış
sonsuza kadar sürer. Zorlayıcı zamanlayıcı bu durumu keser. `unref` çağrısı önemlidir:
bu zamanlayıcı tek başına sürecin ayakta kalma nedeni olmamalıdır, yoksa her şey
bitse bile süreç sınır dolana kadar bekler.

Çıkış kodu sıfırdır. Denetçi için bu, "istenerek kapandı" demektir ve yeniden
başlatma politikasına göre farklı davranmayı sağlar.

Kapanışta yapılacak başka işler de vardır: bekleyen kütük satırlarını aktarmak, açık
dosyaları kapatmak, elde kalan özeti diske yazmak. Bunların hepsi `close` geri
çağrısının içinde, süreç sonlanmadan yapılır — Süreç Nesnesi dersinde görüldüğü gibi
`exit` kancasında eşzamansız iş yapılamaz.

#### Canlılık ve Hazırlık

İki uç iki ayrı soruya yanıt verir ve karıştırılmaları hizmeti kararsızlaştırır.

**Canlılık (liveness)** sorusu: süreç yaşıyor mu, yoksa öldürülüp yeniden mi
başlatılmalı? Yanıtı yalnızca sürecin kendi durumuna bakar; bağımlılıklarını
denetlemez.

**Hazırlık (readiness)** sorusu: bu süreç şu anda istek alabilir mi? Yanıtı açılış
tamamlandı mı, kapanış başladı mı gibi geçici durumlara bakar.

Ayrımın gerekçesi şudur: bir bağımlılık geçici olarak erişilemez olduğunda süreç
hazır değildir ama canlıdır. Canlılık ucu bağımlılığı denetlerse, bağımlılığın kısa
bir kesintisi bütün süreçlerin yeniden başlatılmasına yol açar — hem kesintiyi uzatan
hem de kendi başına arıza üreten bir davranış.

Uçlar hafif olmalıdır: her denetim çağrısı bir veritabanı sorgusu yapıyorsa, denetimin
kendisi yüke dönüşür.

#### Yeniden Başlatma Sözleşmesi

Süreç öldüğünde yeniden başlatmak sürecin işi değildir; kendi kendini yeniden
başlatamaz. Bu iş, işletim sisteminin hizmet denetçisine veya Küme ve İşçi İş
Parçacıkları dersindeki birincil sürece aittir.

Sürece düşen üç şey vardır.

**Anlamlı çıkış kodu vermek.** Sıfır istenen kapanış, sıfırdan farklı bir değer arıza
demektir. Yapılandırma Yönetimi dersinde yapılandırma hatası için ayrı bir kod
seçilmesinin nedeni budur: denetçi, düzelmeyeceği belli olan bir hatayı sonsuza kadar
yeniden denemek yerine durabilir.

**Hızlı ve yinelemeye dayanıklı açılmak.** Yeniden başlatma bir kurtarma yöntemiyse,
açılışın kendisi güvenilir olmalıdır. Açılışta yapılan iş, yarım kalmış bir önceki
çalışmanın izlerini bulduğunda hata vermemelidir — Dosya Sistemi dersindeki geçici
ada yazıp yerine koyma kalıbı bu güvenceyi sağlar.

**Yeniden başlatma fırtınasını hesaba katmak.** Hemen çöken bir süreci hemen yeniden
açmak, saniyede yüzlerce açılış üretir ve makineyi meşgul eder. Denetçiler bunu artan
gecikmeyle önler; sürecin bu gecikmeyi kısaltacak biçimde davranmaması, örneğin
sağlıksız durumda sahte bir başarı bildirmemesi gerekir.

Sürecin kendi kendine yeniden başlatma denemesi ise ayrı bir yanlıştır: bozulmuş bir
durumdan çıkmanın yolu, o durumu taşıyan belleği bırakmaktır. Yeniden başlatmanın
işe yaramasının nedeni de budur — Hata Yönetimi Stratejisi dersinde son çare
kancasının "kaydet ve çık" ile sınırlanmasının gerekçesi.

#### Özet

- Kapanma isteği bir sinyaldir; dinleyici takıldığında varsayılan sonlanma davranışı
  yerini programın kararına bırakır.
- Zarif kapanma dört aşamalıdır: hazır olmadığını bildir, yeni bağlantı alma, süren
  işi bitir, süre sınırında kalanları kes.
- Zorlayıcı zamanlayıcı `unref` ile işaretlenir; aksi hâlde her şey bittiğinde bile
  süreç sınır dolana kadar ayakta kalır.
- Canlılık ucu yalnızca sürecin kendi durumuna, hazırlık ucu geçici duruma bakar;
  canlılık ucunun bağımlılık denetlemesi kesintileri yayar.
- Yeniden başlatma denetçinin işidir; sürece düşen anlamlı çıkış kodu vermek, hızlı
  ve yinelemeye dayanıklı açılmak ve fırtınayı beslememektir.

#### Sonraki Adım

Ölçüm toplayıcı çalışır durumda: yapılandırılır, hataları sınıflandırır, kütüğünü
yapılandırılmış yazar, sınanır, izlenir ve düzgün kapanır. Geriye tek bir soru
kalıyor — bu kodun yeniden kullanılabilir bölümü başka projelere nasıl taşınır?
Son ders paketleme sözleşmesini ele alır: giriş noktalarının bildirilmesi, iç
dosyaların gizlenmesi ve sürüm yayımının kuralları.

### Paket Yayımlama

Source: https://academia.sh/tr/kurslar/nodejs/paket-yayimlama

Paket bildiriminin dışa aktarım sözleşmesi, koşullu girişler, iç yolların gizlenmesi, çalıştırılabilir giriş noktası ve yayım öncesi denetim.

Önceki ders ölçüm toplayıcıyı çalışır ve kapanabilir hâle getirdi. Geriye tek bir
soru kalıyor: bu kodun yeniden kullanılabilir bölümü — özetleyici, kütük, hata
sınıfları — başka projelere nasıl taşınır?

Yanıt, kodu bir paket sözleşmesiyle sarmalamaktır. Modüller, Araçlar ve Ekosistem
kursu paketleri tüketen taraftan anlatmıştı; bu ders yayımlayan tarafa geçer.

#### Paket Bildirimi Bir Sözleşmedir

Bildirim dosyası, paketin adını ve sürümünü taşımanın ötesinde bir iş görür: paketin
dışarıya ne söz verdiğini tanımlar. Modül Çözümleme dersinde bu haritanın tüketici
tarafındaki etkisi görülmüştü; burada yazarın kararları ele alınacak.

```
paket/
  package.json
  kaynak/
    dizin.mjs
    dizin.cjs
    ozet.mjs
    ic-bicim.mjs
```

Bildirim dosyası paketin kök dizinindedir; dosyalar da aşağıda bu düzene yazılır:

```sh
mkdir -p paket/kaynak
cat > paket/package.json <<'SON'
{
  "name": "olcum-cekirdek",
  "version": "1.2.0",
  "type": "module",
  "engines": { "node": ">=20" },
  "exports": {
    ".": {
      "import": "./kaynak/dizin.mjs",
      "require": "./kaynak/dizin.cjs"
    },
    "./ozet": "./kaynak/ozet.mjs"
  },
  "imports": {
    "#ic/bicim": "./kaynak/ic-bicim.mjs"
  },
  "files": ["kaynak/"]
}
SON
```

```js
// paket/kaynak/ic-bicim.mjs
export const bicimle = (n) => Number(n.toFixed(2));
```

```js
// paket/kaynak/ozet.mjs
import { bicimle } from '#ic/bicim';
export const ortalama = (s) => bicimle(s.reduce((t, x) => t + x, 0) / s.length);
```

```js
// paket/kaynak/dizin.mjs
export const giris = 'ES modul girisi';
```

```js
// paket/kaynak/dizin.cjs
module.exports = { giris: 'CommonJS girisi' };
```

Alanların her biri ayrı bir söz verir.

`exports`, dışarıdan hangi yolların içe aktarılabileceğini sabitler. Haritada
olmayan bir dosya, yerinde dursa bile erişilemez. Bu, paketin iç düzenini
yeniden düzenleme özgürlüğü verir: harita korunduğu sürece dosyaların yeri
değişebilir.

`imports`, paketin kendi içindeki takma adlarıdır. `#` ile başlayan bu adlar yalnızca
paketin kendi dosyalarından çözülür ve dışarıdan görünmez. Uzun göreli yolları
kısaltmanın, dışa açılma riski taşımayan yoludur.

`type`, uzantısı `.js` olan dosyaların hangi modül biçiminde yorumlanacağını belirler.
Bu alan paketin kendi bildiriminden okunur; onu kullananın bildirimi etkilemez.

`engines`, paketin hangi çalışma zamanı sürümlerinde sınandığını bildirir. Çalışma
zamanı bu alanı zorlamaz; paket yöneticisi kurulumda uyarır veya reddeder. Alan bir
belge değil, bir sözdür: kullanılan bir arayüz belirli bir sürümden itibaren
bulunuyorsa, alt sınır ona göre yazılır.

`files`, yayımlanan arşive hangi yolların gireceğini belirler. Testler, örnekler ve
geliştirme yapılandırmaları arşiv dışında bırakılır; kullanıcı onları indirmez.

#### Koşullu Girişler

Kök girişin iki karşılığı var: `import` ile geldiğinde bir dosya, `require` ile
geldiğinde başka bir dosya çözülür. Bu, aynı paketi iki modül biçiminden de
kullanılabilir kılar.

Paketi tüketen bir dizin kuralım. Paket yöneticisi yerel bir paketi bağımlılık
dizinine bağ olarak yerleştirir; aynı düzeni elle de kurabiliriz:

```sh
mkdir -p paket/tuketen/node_modules
ln -s ../.. paket/tuketen/node_modules/olcum-cekirdek
printf '{ "name": "tuketen", "type": "module" }\n' > paket/tuketen/package.json
```

```js
// paket/tuketen/dene.mjs
import { giris } from 'olcum-cekirdek';
import { ortalama } from 'olcum-cekirdek/ozet';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);

console.log('import kosulu :', giris);
console.log('require kosulu:', require('olcum-cekirdek').giris);
console.log('alt yol       :', ortalama([21.4, 21.9, 22.3]));
```

```sh
node paket/tuketen/dene.mjs
```

```
import kosulu : ES modul girisi
require kosulu: CommonJS girisi
alt yol       : 21.87
```

Aynı ad, iki farklı dosyaya çözüldü. Üçüncü satır ise alt yolun çalıştığını ve o
modülün iç takma adı üzerinden başka bir dosyayı kullanabildiğini gösteriyor.

İki biçimi birlikte sunmanın bir tuzağı vardır: bir modül aynı süreçte iki kez
değerlendirilebilir; Modül Çözümleme dersinde anlatıldığı gibi teklik çözülen adrese
bağlıdır ve iki dosya iki ayrı adrestir. Paket durum tutuyorsa — bir sayaç, bir
kayıt defteri — iki kopya iki ayrı durum demektir. Durum tutan paketler için tek
biçim sunmak daha güvenlidir.

Haritada olmayan yollar erişime kapalıdır:

```sh
(cd paket/tuketen && node -e "import('olcum-cekirdek/kaynak/ic-bicim.mjs').catch(e => console.log(e.code))")
```

```
ERR_PACKAGE_PATH_NOT_EXPORTED
```

İç takma adlar da dışarıdan çözülemez:

```sh
(cd paket/tuketen && node -e "import('#ic/bicim').catch(e => console.log(e.code))")
```

```
ERR_PACKAGE_IMPORT_NOT_DEFINED
```

İkinci hata, takma adın tüketen paketin kendi bildiriminde tanımlı olmadığını söylüyor.
`#` ile başlayan adlar her paket için ayrı ayrı tanımlıdır; başka bir paketin iç
adları ödünç alınamaz.

#### Çalıştırılabilir Giriş

Komut Satırı Uygulamaları dersindeki araç bir pakete konulabilir. Bildirimdeki `bin`
alanı, paket kurulduğunda hangi adın hangi dosyaya bağlanacağını söyler:

```json
{
  "name": "olcum-araclari",
  "version": "1.0.0",
  "type": "module",
  "bin": { "olcum-topla": "./kaynak/topla.mjs" },
  "files": ["kaynak/"]
}
```

İki koşul gerekir. Dosyanın ilk satırında yorumlayıcı bildirimi bulunmalıdır —
`#!/usr/bin/env node` satırı, çekirdeğe dosyanın hangi programla çalıştırılacağını
söyler. İkincisi, dosyanın çalıştırma iznine sahip olmasıdır; paket yöneticisi bunu
genellikle kurulum sırasında ayarlar, ama depoya doğru izinle işlenmesi güvenlidir.

Kütüphane ile aracı aynı pakette birleştirmek bir seçimdir. Ayrı paketler, kütüphaneyi
kullananların araç bağımlılıklarını indirmesini önler; tek paket, sürüm uyumunu
kendiliğinden sağlar.

#### Yayım Öncesi Denetim

Yayım geri alınamayan bir işlemdir: bir sürüm numarası bir kez yayımlanır ve
içeriği değiştirilemez. Bu yüzden yayımdan önce dört denetim yapılır.

**Arşivin içeriği.** `files` alanı doğru yazılmadığında iki yönlü hata olur: gerekli
bir dosya dışarıda kalır ve paket kurulduğunda çalışmaz, ya da gereksiz dosyalar
girer ve arşiv şişer. Paket yöneticileri, arşivi üretmeden içeriğini listeleyen bir
kuru çalışma kipi sunar; yayımdan önce bu liste okunur.

**Giriş noktalarının çalışması.** Paketi tüketen küçük bir dizin kurup — yukarıdaki
gibi — her girişi içe aktarmak, haritadaki bir yazım yanlışını yayımdan önce
yakalar. Yayımlanmış bir paketin girişinin kırık olması, sürüm çekilse bile
kullanıcıların önbelleğinde kalır.

**Sürüm numarasının anlamı.** Modüller, Araçlar ve Ekosistem kursunda anlamlı
sürümleme kuralları anlatılmıştı. Yayımlayan taraf için kural şu soruya indirgenir:
bu sürüme geçen bir kullanıcının kodu bozulur mu? Bozuluyorsa büyük sürüm artar.
Dışa aktarım haritasından bir giriş kaldırmak, bir alanın türünü değiştirmek ve
desteklenen en düşük çalışma zamanı sürümünü yükseltmek kırıcı değişikliklerdir.

**Gizli değer sızıntısı.** Yapılandırma Yönetimi dersindeki ortam dosyası, arşive
girmemelidir. Sürüm Kontrolüne Giriş kursundaki yok sayma listesi bu dosyayı depodan
uzak tutar; `files` alanı ise arşivden. İki denetim birbirinin yerine geçmez —
depoda bulunmayan bir dosya yerel çalışma dizininde durabilir ve arşive girebilir.

Yayım işleminin kendisi paket yöneticisinin komutudur ve bir kaydın hesabına
erişim gerektirir. Erişimin ikinci bir etmenle korunması, yayım hakkının en dar
kümeyle sınırlanması ve yayımın el ile değil, sürüm etiketiyle tetiklenen bir
otomasyon adımından yapılması yaygın uygulamalardır.

#### Özet

- Paket bildirimi, paketin dışarıya verdiği sözü tanımlar; dışa aktarım haritası
  genel arayüzü sabitler ve iç düzeni yeniden düzenleme özgürlüğü verir.
- Koşullu girişler aynı paketi iki modül biçiminden kullanılabilir kılar; durum tutan
  paketlerde iki kopya iki ayrı durum anlamına gelir.
- `#` ile başlayan iç takma adlar yalnızca kendi paketinden çözülür; başka paketten
  erişilemez.
- Çalıştırılabilir giriş, ad ile dosya eşlemesi, yorumlayıcı bildirimi ve çalıştırma
  izni olmak üzere üç koşula bağlıdır.
- Yayım geri alınamaz; arşiv içeriği, giriş noktaları, sürüm numarasının anlamı ve
  gizli değer sızıntısı yayımdan önce denetlenir.

#### Kurs Kapanışı

Kurs, tek bir soruyla başladı: aynı dil, tarayıcı yerine bir sunucu sürecinde
çalıştığında ne değişir? Yanıt üç katmanda kuruldu.

**Çalışma zamanı modeli.** Barındıran ortam kavramı, dil çekirdeğini dış dünyaya
erişimden ayırdı. Olay döngüsünün aşamaları ölçüldü; zamanlayıcı gecikmesinin bir alt
sınır olduğu ve engelleyen işin bu sınırı kendi süresi kadar büyüttüğü gösterildi.
Süreç nesnesi, argümanları, ortamı, akışları ve çıkış kodunu programın eline verdi.
Modül çözümleme, bir adın hangi dosyaya karşılık geldiğini kesinleştirdi.

**Yerleşik modüller.** Dosya sistemi üç arayüzüyle ele alındı ve seçim ölçütü olay
döngüsüne bağlandı. Yollar veri olarak işlendi; kök dizin dışına çıkışı engelleyen
denetim yazıldı. Akışlar, parça sınırının kayıt sınırından farklı olduğu gözlemiyle
kuruldu ve geri basınç ölçüldü: dönüş değerini yok sayan üretici tamponu elli kayda
çıkarırken, dinleyen üretici dördü aşmadı. Arabellekler ham baytı, olay yayıcı
gözlemci kalıbını, HTTP sunucusu istek ve yanıtın akış doğasını getirdi. Alt süreçler
ve çok çekirdek kullanımı, işi sürecin dışına çıkarmanın iki yolunu ayırdı.

**Uygulama yazımı.** Komut satırı arayüzü, yapılandırma, hata sınıflandırması,
yapılandırılmış kütük, test, profilleme, zarif kapanma ve paketleme — hepsi tek bir
somut iş üzerinde geliştirildi. Ölçüm toplayıcı, on iki satırlık bir dosyayı okuyan
bir betikten başlayıp; akışla okuyan, HTTP üzerinden sunan, sınanan, izlenen ve
sinyal aldığında süren isteklerini tamamlayarak kapanan bir hizmete dönüştü.

Bu kurs, JavaScript ve TypeScript müfredatının son kursudur. Buradan iki yön açılır.

**Frontend Geliştirme** müfredatı, aynı dilin öbür barındıran ortamına döner:
belge yapısı, oluşturma stratejileri, bileşen mimarisi, erişilebilirlik ve başarım.
Bu kursta öğrenilen modül çözümleme, paketleme ve olay modeli oradaki yapı
araçlarının dayanağıdır.

**Backend Geliştirme** müfredatı ise bu kursun bıraktığı yerden sürer: burada elle
kurulan istek dağıtımı, yapılandırma, günlükleme ve hata katmanı orada bir uygulama
mimarisi olarak ele alınır; üstüne API sözleşmesi, veri erişimi, kimlik doğrulama,
eşzamansız işleme ve gözlemlenebilirlik katmanları eklenir. Ölçüm toplayıcının
`/ozet` ucu, orada tasarımı gerekçelendirilen bir kaynak modelinin ilk örneğidir.

İki yönde de bu kursta kurulan model geçerli kalır: olay güdümlü, tek iş parçacıklı,
akışlarla çalışan bir çalışma zamanı. Çerçeveler ve kütüphaneler bu modeli gizler,
değiştirmez — ve gizlenmiş bir modelin arızası, ancak modeli bilen tarafından
çözülür.

