---
title: 'Oluşturma Stratejileri ve Altyapı'
source: 'https://academia.sh/tr/kurslar/olusturma-stratejileri'
language: tr
updated: '2026-08-17T04:41:04+00:00'
license: 'CC BY-SA 4.0'
lessons: 19
outcomes:
    - 'İstemci, sunucu ve derleme zamanı oluşturmayı ödünleşimleriyle seçer'
    - 'Paketleme ve varlık optimizasyonunu yapılandırır'
    - 'Dağıtım ve ortam yönetimini kurar'
---

# Oluşturma Stratejileri ve Altyapı

Bir arayüzün işaretlemesinin nerede ve ne zaman üretileceği kararı; istemci, sunucu, derleme zamanı ve kenar modellerinin ölçülebilir ödünleşimleri ile bu kararı izleyen derleme, paketleme ve dağıtım altyapısı.

## Oluşturma Modelleri

İşaretlemenin istemcide, sunucuda, derleme zamanında ya da kenarda üretilmesi; artımlı yeniden üretim, canlandırma ve akışlı gönderim ile bu modeller arasındaki seçimin ölçütleri.

### İstemcide Oluşturma

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/istemcide-olusturma

İşaretlemenin tarayıcıda üretilmesi; boş kabuğun ne taşıdığı, ilk içeriğe kadar uzanan seri istek zinciri ve bu modelin ilk boyamada ödediği bedelle sonraki gezinmelerde kazandığı hız.

Bir önceki kurs uygulamanın iç mimarisini kurdu: adresler görünümlere eşlendi, durum tek bir
yerden yönetildi, veri katmanı isteği ve yeniden denemeyi üstlendi ve formlar doğrulama
kurallarını paylaştı. Bütün bunlar uygulamanın **ne yaptığını** tanımlar. Açıkta kalan soru şudur:
bu uygulamanın kullanıcıya gönderilen işaretlemesi **nerede** ve **ne zaman** üretilir?

Kuzey Yamaç Ölçüm İstasyonu sitesi bu soruyu somutlaştırır. Sitede bir istasyon tanıtım
sayfası, dakikada bir değişen bir ölçüm listesi, geçmiş günlerin binlerce arşiv sayfası ve
istasyon sorumlusuna özel bir panel vardır. Bu sayfaların hepsi aynı yerde üretilmek
zorunda değildir. Bu kurs, her biri için ayrı bir karar verir. İlk model, işaretlemeyi
tarayıcıya ürettirendir.

#### Oluşturma Kararı Nedir, Ne Değildir

**Oluşturma (rendering)**, bu kursta veri ile şablonun birleşip HTML işaretlemesine
dönüşmesi anlamına gelir. Karar, bu birleşmenin hangi makinede ve hangi anda olduğuna
ilişkindir: kullanıcının tarayıcısında mı, isteğe yanıt veren sunucuda mı, yoksa istek
gelmeden çok önce derleme sırasında mı.

Bu, Tarayıcı ve Web Platformu kursunda geçen **çizim hattı** ile karıştırılmamalıdır.
Çizim hattı, tarayıcının elindeki işaretlemeyi yerleşim, boyama ve bileşim aşamalarından
geçirip piksele çevirmesidir; işaretleme hangi makinede üretilmiş olursa olsun bu hat
tarayıcıda çalışır. Oluşturma kararı hattın **girdisinin** nereden geldiğini belirler,
hattın kendisini değiştirmez.

Ayrımı korumak için kurs boyunca iki sözcük ayrı tutulur: işaretlemenin üretilmesi
**oluşturma**, tarayıcının piksele çevirmesi **çizim**.

#### Sunucunun Gönderdiği Şey

İstemcide oluşturmada sunucu, veri içermeyen bir belge ve bir betik gönderir. Belgenin
görevi betiği yüklemek ve betiğin yazacağı boş bir kap sağlamaktır.

```js
// istemci-sunucu.mjs — istasyon sayfasini istemcide olusturan sunucu
import { createServer } from "node:http";

const OLCUMLER = [
  { zaman: "06:00", sicaklik: -4.2, nem: 72 },
  { zaman: "07:00", sicaklik: -3.8, nem: 70 },
  { zaman: "08:00", sicaklik: -2.1, nem: 66 },
];

const KABUK = `<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac Olcum Istasyonu</title>
<script src="/uygulama.js" defer></script></head>
<body><div id="istasyon"></div></body></html>
`;

const BETIK = `const kok = document.getElementById("istasyon");
const yanit = await fetch("/api/olcumler");
const olcumler = await yanit.json();
kok.innerHTML = "<h1>Kuzey Yamac Olcum Istasyonu</h1><ul>" +
  olcumler.map((o) => "<li>" + o.zaman + " " + o.sicaklik + " C, %" + o.nem + "</li>").join("") +
  "</ul>";
`;

const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

createServer(async (istek, yanit) => {
  yanit.sendDate = false; // ciktiyi belirlenimci kilmak icin Date basligi yazilmaz
  if (istek.url === "/") {
    yanit.setHeader("Content-Type", "text/html; charset=utf-8");
    yanit.setHeader("Content-Length", Buffer.byteLength(KABUK));
    yanit.writeHead(200).end(KABUK);
  } else if (istek.url === "/uygulama.js") {
    yanit.setHeader("Content-Type", "text/javascript; charset=utf-8");
    yanit.setHeader("Content-Length", Buffer.byteLength(BETIK));
    yanit.writeHead(200).end(BETIK);
  } else if (istek.url === "/api/olcumler") {
    await bekle(40); // veri kaynagindan okuma isi
    const govde = JSON.stringify(OLCUMLER);
    yanit.setHeader("Content-Type", "application/json");
    yanit.setHeader("Content-Length", Buffer.byteLength(govde));
    yanit.writeHead(200).end(govde);
  } else {
    yanit.writeHead(404).end();
  }
}).listen(8171, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8171"));
```

Aşağıdaki betik bu dosyayla aynı dizinde çalıştırılır. Bağlantı noktası olarak seçilen
8171 keyfîdir ve boş olmalıdır; kullanımdaysa iki dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# istemci-sunucu.mjs'yi baslatir, uc istegi sirayla olcer, sonra durdurur.
node istemci-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

BICIM='  ilk bayt %{time_starttransfer} s   govde %{size_download} B\n'

echo "--- ilk belgenin govdesi ---"
curl -sS http://127.0.0.1:8171/

echo "--- belgede olcum degeri geciyor mu? ---"
curl -sS http://127.0.0.1:8171/ | grep -c -- "-4.2"

echo "--- zincirin uc adimi (sirayla) ---"
echo "1) /"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8171/
echo "2) /uygulama.js"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8171/uygulama.js
echo "3) /api/olcumler"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8171/api/olcumler

kill "$sunucu"
```

```
--- ilk belgenin govdesi ---
<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac Olcum Istasyonu</title>
<script src="/uygulama.js" defer></script></head>
<body><div id="istasyon"></div></body></html>
--- belgede olcum degeri geciyor mu? ---
0
--- zincirin uc adimi (sirayla) ---
1) /
  ilk bayt 0.000732 s   govde 199 B
2) /uygulama.js
  ilk bayt 0.000669 s   govde 300 B
3) /api/olcumler
  ilk bayt 0.042841 s   govde 130 B
```

Süre alanları çalıştırıldığı makineye ve o andaki yüke bağlıdır; her çalıştırmada farklı
çıkar. Anlamlı olan sayıların kendisi değil, aralarındaki orandır: ilk iki istek yerel
bağlantıda mikrosaniyeler sürerken üçüncüsü, yapay olarak eklenmiş 40 milisaniyelik veri
işi yüzünden yaklaşık altmış kat uzundur. Gerçek bir dağıtımda bu üç isteğin her birine
bir de ağ turu eklenir.

İkinci çıktı satırı modelin tanımlayıcı özelliğidir: ilk belgede hiçbir ölçüm değeri
yoktur. Belgeyi okuyan bir istemci — arama motoru gezgini, bağlantı önizlemesi üreten bir
hizmet, betik çalıştırmayan bir okuyucu — boş bir kap görür.

#### Seri Zincirin Aritmetiği

Üç isteğin her biri bir öncekinin sonucuna bağlıdır. Belge gelmeden betiğin adresi
bilinmez; betik yürütülmeden veri isteği doğmaz. Bu bağımlılık, sürelerin toplanmasına yol
açar. Aşağıdaki hesap, ölçüm değil, açıkça yazılmış varsayımların aritmetiğidir.

```js
// zincir.mjs — istemcide olusturmanin ilk icerige kadar olan seri zinciri
// Girdiler varsayimdir; cikti bu varsayimlarin aritmetigidir, olcum degildir.
const GIDIS_DONUS = 60;      // ms, tek tur
const SUNUCU_BELGE = 5;      // ms, kabugu uretme isi
const SUNUCU_VERI = 40;      // ms, veri ucunun isi
const BANT = 1.5 * 1024;     // KB/s
const BETIK_KB = 180;
const YURUTME_MS_KB = 0.35;  // KB basina ayristirma + yurutme

const aktarim = (kb) => (kb / BANT) * 1000;

const adimlar = [
  ["belge istegi (tur)", GIDIS_DONUS],
  ["kabugu uretme", SUNUCU_BELGE],
  ["kabuk aktarimi (0.2 KB)", aktarim(0.2)],
  ["betik istegi (tur)", GIDIS_DONUS],
  [`betik aktarimi (${BETIK_KB} KB)`, aktarim(BETIK_KB)],
  ["betik ayristirma + yurutme", BETIK_KB * YURUTME_MS_KB],
  ["veri istegi (tur)", GIDIS_DONUS],
  ["veri ucunun isi", SUNUCU_VERI],
  ["veri aktarimi (0.1 KB)", aktarim(0.1)],
  ["agaci kurma + boyama", 8],
];

let toplam = 0;
console.log("adim".padEnd(30) + "sure".padStart(8) + "birikimli".padStart(12));
for (const [ad, sure] of adimlar) {
  toplam += sure;
  console.log(ad.padEnd(30) + sure.toFixed(1).padStart(8) + toplam.toFixed(1).padStart(12));
}
console.log("-".repeat(50));
console.log("ilk icerik boyamasi".padEnd(30) + toplam.toFixed(1).padStart(20));

// Kabukta gorunen sey: yalnizca ilk iki adimin sonunda bos bir belge.
const bosBelge = GIDIS_DONUS + SUNUCU_BELGE + aktarim(0.2);
console.log("bos kabuk ekranda".padEnd(30) + bosBelge.toFixed(1).padStart(20));
console.log("bos ekranda gecen oran".padEnd(30) +
  ((bosBelge / toplam) * 100).toFixed(0).padStart(19) + "%");
```

```
adim                              sure   birikimli
belge istegi (tur)                60.0        60.0
kabugu uretme                      5.0        65.0
kabuk aktarimi (0.2 KB)            0.1        65.1
betik istegi (tur)                60.0       125.1
betik aktarimi (180 KB)          117.2       242.3
betik ayristirma + yurutme        63.0       305.3
veri istegi (tur)                 60.0       365.3
veri ucunun isi                   40.0       405.3
veri aktarimi (0.1 KB)             0.1       405.4
agaci kurma + boyama               8.0       413.4
--------------------------------------------------
ilk icerik boyamasi                          413.4
bos kabuk ekranda                             65.1
bos ekranda gecen oran                         16%
```

Tabloda üç kalem baskındır: üç ağ turu, betiğin aktarımı ve betiğin yürütülmesi. Üçü de
kullanıcının gördüğü ilk içeriğin önünde durur. Kabuk ekrana geldiğinde toplam sürenin yalnızca
altıda biri geçmiştir; kalan altıda beşinde ekranda içerik yoktur.

Bu tablo aynı zamanda hangi iyileştirmenin ne kadar getirdiğini gösterir. Betiği yarıya
indirmek yaklaşık 90 milisaniye kazandırır. Veri isteğini belge isteğiyle birleştirmek bir
turu ve veri ucunun işini zincirin dışına çıkarır. Ağ turlarının süresi ise kod
değişikliğiyle azalmaz; mesafeye bağlıdır ve altıncı ders bu kalemi ayrıca ele alır.

#### Modelin Kazandırdıkları

İlk yükleme pahalıdır, ama bedelin karşılığı vardır ve bu karşılık göz ardı edilirse model
haksız yere reddedilir.

Betik bir kez yürüdükten sonra **sonraki gezinmeler ağdan bağımsızlaşır**. Uygulama
Mimarisi kursunda kurulan istemci yönlendiricisi adres değiştiğinde yeni bir belge
istemez; eldeki durumdan yeni görünümü üretir. Ölçüm listesinden arşiv sayfasına geçiş,
belge indirmeden gerçekleşir.

Sunucu tarafı **durumsuz ve ucuzdur**. Sunucunun gönderdiği belge her kullanıcı için
aynıdır; kişiye göre değişen hiçbir şey üretmez. Böyle bir belge statik dosya olarak
sunulabilir, uzun ömürle önbelleklenebilir ve bir içerik dağıtım ağının kopyalarından
verilebilir.

**İstemci–sunucu sınırı belirgindir.** Sunucu yalnızca veri uçları sunar; işaretlemeyi
üretme sorumluluğu tümüyle istemcidedir. Aynı veri uçları bir masaüstü ya da mobil kabuk
tarafından da kullanılabilir; bu sınır, arayüzün çoğaltılmasını kolaylaştırır.

Uzun süre açık kalan, sık etkileşim alan ve her etkileşimde sunucuya dönmesi gerekmeyen
arayüzler bu modelin doğal alanıdır. İstasyon sorumlusunun paneli — ölçüm eşiklerini
düzenlediği, grafikleri yakınlaştırdığı, kayıtları süzdüğü ekran — buna örnektir.

#### Bedelin Ağırlaştığı Yerler

Aynı ödünleşim, üç durumda modeli yanlış seçim hâline getirir.

**İlk ziyaretin sık olduğu sayfalarda** bedel her seferinde ödenir. Bir arama sonucundan
gelip tek sayfa okuyup ayrılan ziyaretçi, sonraki gezinmelerin hızından hiç
yararlanmaz; yalnızca ilk yüklemenin gecikmesini görür.

**İşaretlemeyi okuyan istemcilerde** içerik yoktur. Çıktının ikinci satırı bunu
göstermişti: belgede hiçbir ölçüm değeri geçmez. İçeriğin makine tarafından okunmasına
bağlı sayfalarda bu tek başına belirleyicidir.

**Yavaş aygıtlarda betiğin yürütülmesi** aktarımından pahalı hâle gelir. Tabloda 63
milisaniye görünen ayrıştırma ve yürütme kalemi, işlemci gücü birkaç kat düşük bir aygıtta
aynı oranda büyür; üstelik bant genişliği arttıkça bu kalem toplamın içinde daha büyük bir
pay tutar. Betiğin boyutu yalnızca indirme süresini değil, cihazın harcadığı işi de
belirler.

Dördüncü bir kırılganlık, betiğin hiç yürümemesidir. Aktarım kesilirse, bir hata betiği
durdurursa ya da bir uzantı araya girerse kullanıcının gördüğü şey boş bir belgedir. Sunucu
tarafında üretilmiş bir işaretlemede aynı arıza yalnızca etkileşimi kaybettirir, içeriği
değil.

#### Özet

- Oluşturma kararı, veri ile şablonun hangi makinede ve hangi anda işaretlemeye
  dönüştüğünü belirler; tarayıcının çizim hattı bu karardan bağımsız olarak her durumda
  istemcide çalışır.
- İstemcide oluşturmada ilk belge veri taşımaz; ölçüm değerleri işaretlemede geçmez ve
  belgeyi betik çalıştırmadan okuyan istemciler boş bir kap görür.
- İlk içeriğe kadar olan yol seridir: belge, betik ve veri istekleri birbirini bekler;
  toplam süreye üç ağ turu, betiğin aktarımı ve betiğin yürütülmesi eklenir.
- Model, ilk yüklemeden sonra gezinmeleri ağdan bağımsızlaştırır ve sunucuyu durumsuz
  bırakır; uzun oturumlu, etkileşim yoğun ekranlar için uygundur.
- Bedel, ilk ziyaretin sık olduğu, işaretlemenin makine tarafından okunduğu ve aygıt
  gücünün düşük olduğu durumlarda ağırlaşır.

#### Sonraki Adım

Bu dersteki zincirin üç ağ turundan ikisi, veri ile işaretlemenin ayrı ayrı taşınmasından
doğuyordu. İkisi tek yanıtta birleştirilirse zincir kısalır: sunucu, veriyi okuyup şablonu
kendisi işleyerek içeriği hazır gönderebilir. Bu, ilk içeriğin görünme anını öne çeker ama
sunucunun her istek için iş yapmasını gerektirir ve önbelleklenmesi kolay bir yanıtı
kişiye göre değişen bir yanıta dönüştürür. Sonraki ders bu takası ölçer: aynı istasyon
sayfası sunucuda üretildiğinde ilk bayt süresi, gövde boyutu ve içerik nasıl değişir.

### Sunucuda Oluşturma

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/sunucuda-olusturma

İşaretlemenin isteğe yanıt verilirken üretilmesi; ilk bayt süresinin nasıl büyüdüğü, seri istek zincirinin tek tura inmesi, ağ turunun ağırlığı ve sunucunun istek başına ödediği hesaplama bedeli.

Önceki ders istemcide oluşturmanın ilk içeriğe kadar üç seri istek gerektirdiğini gösterdi:
belge, betik, veri. Bu üç isteğin ikisi, veri ile işaretlemenin ayrı taşınmasından doğar.
Sunucu veriyi zaten okuyabildiğine göre şablonu da kendisi işleyip içeriği hazır
gönderebilir.

Bu ders o değişikliği yapar ve sonucunu aynı ölçülerle sınar: ilk bayt süresi, gövde
boyutu ve belgede içeriğin bulunup bulunmaması.

#### Aynı Sayfa, İki Model

Karşılaştırmanın adil olması için iki model aynı veri kaynağını, aynı gecikmeyi ve aynı
şablonu kullanır. Aşağıdaki sunucu her ikisini de sunar: `/istemci` yolu boş kabuğu,
`/sunucu` yolu işaretlemesi hazır belgeyi verir.

```js
// iki-model.mjs — ayni istasyon sayfasini iki modelde sunan tek sunucu
import { createServer } from "node:http";

const OLCUMLER = [
  { zaman: "06:00", sicaklik: -4.2, nem: 72 },
  { zaman: "07:00", sicaklik: -3.8, nem: 70 },
  { zaman: "08:00", sicaklik: -2.1, nem: 66 },
];

const bekle = (ms) => new Promise((c) => setTimeout(c, ms));
const okuOlcumler = async () => { await bekle(40); return OLCUMLER; }; // veri kaynagi isi

const liste = (olcumler) =>
  olcumler.map((o) => `<li>${o.zaman} ${o.sicaklik} C, %${o.nem}</li>`).join("");

const belge = (govde, betik) => `<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac Olcum Istasyonu</title>
<script src="${betik}" defer></script></head>
<body>${govde}</body></html>
`;

const KABUK = belge('<div id="istasyon"></div>', "/uygulama.js");

const BETIK_ISTEMCI = `const kok = document.getElementById("istasyon");
const olcumler = await (await fetch("/api/olcumler")).json();
kok.innerHTML = "<h1>Kuzey Yamac Olcum Istasyonu</h1><ul>" +
  olcumler.map((o) => "<li>" + o.zaman + " " + o.sicaklik + " C, %" + o.nem + "</li>").join("") +
  "</ul>";
`;

const BETIK_SUNUCU = `document.querySelector("#yenile")
  .addEventListener("click", () => location.reload());
`;

const gonder = (yanit, tip, govde) => {
  yanit.setHeader("Content-Type", tip);
  yanit.setHeader("Content-Length", Buffer.byteLength(govde));
  yanit.writeHead(200).end(govde);
};

const HTML = "text/html; charset=utf-8";
const JS = "text/javascript; charset=utf-8";

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  switch (istek.url) {
    case "/istemci":
      return gonder(yanit, HTML, KABUK);
    case "/uygulama.js":
      return gonder(yanit, JS, BETIK_ISTEMCI);
    case "/api/olcumler":
      return gonder(yanit, "application/json", JSON.stringify(await okuOlcumler()));
    case "/sunucu": {
      const olcumler = await okuOlcumler();
      const govde = `<div id="istasyon"><h1>Kuzey Yamac Olcum Istasyonu</h1>` +
        `<ul>${liste(olcumler)}</ul><button id="yenile">Yenile</button></div>`;
      return gonder(yanit, HTML, belge(govde, "/etkilesim.js"));
    }
    case "/etkilesim.js":
      return gonder(yanit, JS, BETIK_SUNUCU);
    default:
      return yanit.writeHead(404).end();
  }
}).listen(8172, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8172"));
```

Tek fark, `/sunucu` dalının veriyi yanıt üretilirken okuyup listeyi işaretlemeye
yazmasıdır. Şablon işlevi ikisinde de aynıdır; ayrı olan, çağrıldığı yerdir.

#### Ölçüm

Aşağıdaki betik bu dosyayla aynı dizinde çalıştırılır. Bağlantı noktası 8172 keyfîdir ve
boş olmalıdır; kullanımdaysa iki dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# iki-model.mjs'yi baslatir, iki modeli ayni olculerle karsilastirir, sonra durdurur.
node iki-model.mjs > /dev/null &
sunucu=$!
sleep 1
curl -sS -o /dev/null http://127.0.0.1:8172/istemci   # isinma istegi: olcume girmez

BICIM='ilk bayt %{time_starttransfer} s   govde %{size_download} B\n'

echo "--- istemcide olusturma: ilk belge ---"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8172/istemci
echo "belgede gecen olcum degeri sayisi: $(curl -sS http://127.0.0.1:8172/istemci | grep -o -- '-4.2\|-3.8\|-2.1' | wc -l | tr -d ' ')"

echo "--- sunucuda olusturma: ilk belge ---"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8172/sunucu
echo "belgede gecen olcum degeri sayisi: $(curl -sS http://127.0.0.1:8172/sunucu | grep -o -- '-4.2\|-3.8\|-2.1' | wc -l | tr -d ' ')"

echo "--- sunucuda uretilmis govde ---"
curl -sS http://127.0.0.1:8172/sunucu

echo "--- istemci modelinde icerige kadar uc istegin toplami ---"
toplam=0
for yol in /istemci /uygulama.js /api/olcumler; do
  s=$(curl -sS -o /dev/null -w '%{time_starttransfer}' "http://127.0.0.1:8172$yol")
  printf '  %-14s %s s\n' "$yol" "$s"
  toplam=$(echo "$toplam + $s" | bc)
done
printf '  %-14s %s s\n' "toplam" "$toplam"

kill "$sunucu"
```

```
--- istemcide olusturma: ilk belge ---
ilk bayt 0.000909 s   govde 199 B
belgede gecen olcum degeri sayisi: 0
--- sunucuda olusturma: ilk belge ---
ilk bayt 0.042922 s   govde 358 B
belgede gecen olcum degeri sayisi: 3
--- sunucuda uretilmis govde ---
<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac Olcum Istasyonu</title>
<script src="/etkilesim.js" defer></script></head>
<body><div id="istasyon"><h1>Kuzey Yamac Olcum Istasyonu</h1><ul><li>06:00 -4.2 C, %72</li><li>07:00 -3.8 C, %70</li><li>08:00 -2.1 C, %66</li></ul><button id="yenile">Yenile</button></div></body></html>
--- istemci modelinde icerige kadar uc istegin toplami ---
  /istemci       0.000837 s
  /uygulama.js   0.000796 s
  /api/olcumler  0.042007 s
  toplam         .043640 s
```

Süre alanları makineye ve o andaki yüke bağlıdır; her çalıştırmada farklı çıkar. Yorumu
oranlar taşır.

Üç değişiklik ölçülmüştür. **İlk bayt süresi yaklaşık kırk yedi kat uzamıştır** — 0,0009 saniyeden
0,0429 saniyeye: boş kabuk için sunucu hiçbir iş yapmaz, hazır belge içinse veri kaynağını
beklemek zorundadır. Kabuğun süresi yerel bağlantının gürültü tabanındadır ve çalıştırmadan
çalıştırmaya belirgin biçimde oynar; sabit kalan, sunucuda oluşturmada ilk baytın **veri
kaynağının işi kadar** gecikmesidir. **Gövde yaklaşık 1,8 kat büyümüştür**: işaretleme artık üç
ölçüm satırını da taşır. **Belgede içerik vardır**: istemci modelinde sıfır olan ölçüm değeri
sayısı üçtür.

#### Ağ Turu Olmayan Bir Ölçümün Sınırı

Son bölüm dikkatle okunmalıdır. İstemci modelinde içeriğe kadar geçen toplam süre 0,0436
saniyedir; sunucu modelinin tek isteği 0,0429 saniye sürer. İkisi neredeyse aynıdır.

Bunun nedeni ölçümün geri döngü arayüzünde yapılmasıdır: 127.0.0.1 adresine yapılan bir
istekte ağ turu sıfıra yakındır. Ölçüm bu yüzden yalnızca **sunucu işini** yalıtır ve
sunucu işi iki modelde de aynı 40 milisaniyedir. Sunucuda oluşturmanın kazancı sunucu
işinden değil, **kaç kez gidip gelindiğinden** doğar.

Turun ağırlığı ayrıca hesaplanır. Aşağıdaki hesabın girdileri yukarıdaki ölçümden ve
önceki dersteki betik varsayımlarından gelir.

```js
// tur-etkisi.mjs — ag turunun iki modeldeki agirligi
// Sunucu isi ve aktarim, l02'deki yerel olcumden alinmistir: veri isi 42 ms,
// kabuk ve belge aktarimi yerel baglantida 1 ms'nin altinda kaldigi icin 1 ms sayildi.
const VERI_ISI = 42;      // ms
const AKTARIM = 1;        // ms, her yanit icin
const BETIK_INDIRME = 117; // ms, 180 KB / 1.5 MB/s
const BETIK_YURUTME = 63;  // ms

// istemci modeli: belge turu + betik turu + veri turu, arada betik indirme ve yurutme
const istemci = (tur) =>
  tur + AKTARIM + tur + BETIK_INDIRME + BETIK_YURUTME + tur + VERI_ISI + AKTARIM;

// sunucu modeli: tek tur, sunucu veriyi okur ve isaretlemeyi hazir gonderir
const sunucu = (tur) => tur + VERI_ISI + AKTARIM;

console.log("ag turu".padEnd(10) + "istemci".padStart(10) + "sunucu".padStart(10) +
  "fark".padStart(10) + "oran".padStart(8));
for (const tur of [0, 20, 60, 150]) {
  const i = istemci(tur), s = sunucu(tur);
  console.log(`${tur} ms`.padEnd(10) + `${i} ms`.padStart(10) + `${s} ms`.padStart(10) +
    `${i - s} ms`.padStart(10) + (i / s).toFixed(1).padStart(8));
}
```

```
ag turu      istemci    sunucu      fark    oran
0 ms          224 ms     43 ms    181 ms     5.2
20 ms         284 ms     63 ms    221 ms     4.5
60 ms         404 ms    103 ms    301 ms     3.9
150 ms        674 ms    193 ms    481 ms     3.5
```

Tablo iki şeyi aynı anda söyler. Mutlak fark tur uzadıkça **büyür**: 150 milisaniyelik bir
turda aradaki uçurum yarım saniyeye yaklaşır. Oran ise **küçülür**, çünkü tur her iki
modele de eklenir ve payda da büyür. Sıfır turluk satır, betik indirme ile yürütmenin tek
başına 180 milisaniye tuttuğunu gösterir: bu kalem ağ hiç olmasa bile durmaz.

#### Sunucunun Ödediği Bedel

Kazanılan sürenin bir faturası vardır ve bu fatura istek başınadır.

**Hesaplama işi tekrarlanır.** Boş kabuk her kullanıcı için aynı bayt dizisidir ve bir kez
üretilip sonsuza kadar sunulabilir. Sunucuda üretilen belge her istekte yeniden kurulur:
veri okunur, şablon işlenir, dizgi birleştirilir. İstek hacmi arttıkça bu iş doğrusal
büyür ve sunucunun boyutlandırılmasını belirleyen kalem hâline gelir.

**Yanıt önbelleklenmesi zorlaşır.** Önbellek Stratejileri dersinde kurulan ayrım burada
karşılığını bulur: boş kabuk uzun ömürle saklanabilen bir varlıktır, sunucuda üretilmiş
belge ise değişken içeriktir. İçerik kişiye göre değişiyorsa yanıt hiçbir paylaşımlı
katmanda saklanamaz; değişmiyorsa saklanabilir ama tazelik penceresi içeriğin değişme
sıklığıyla sınırlıdır. Kişiselleştirmenin sayfanın tamamına mı yoksa küçük bir bölümüne mi
dokunduğu, bu yüzden bir tasarım kararıdır.

**Veri kaynağının gecikmesi doğrudan ilk bayt süresine yazılır.** İstemci modelinde
kullanıcı beklerken en azından bir kabuk görür; sunucu modelinde veri kaynağı yavaşladığında
kullanıcı hiçbir şey görmez, çünkü ilk bayt henüz yollanmamıştır. Yanıtın tamamı tek
parçada üretildiği sürece bu bağımlılık kırılamaz.

**Hata alanı genişler.** Veri kaynağı yanıt vermediğinde istemci modelinde kabuk yüklenir
ve arayüz bir hata durumu gösterebilir; sunucu modelinde belgenin kendisi üretilemez. Bu
yüzden sunucuda oluşturan bir uç, veri çağrılarına zaman aşımı koyar ve eksik veriyle de
üretilebilecek bir yedek işaretleme tanımlar.

#### Gelen İşaretleme Etkileşimli Değildir

Ölçülen gövdede bir düğme vardır ama düğme çalışmaz. İşaretleme sunucuda üretilmiştir;
olay dinleyicileri istemcide, betik yürüdükten sonra bağlanır. Belge ekrana geldiği an ile
düğmenin yanıt verdiği an arasında bir aralık kalır.

Bu aralık modelin doğal sonucudur ve ölçülebilir iki büyüklüğü ayırır: **ilk içerik boyası**
belgenin gelmesiyle gerçekleşir, **etkileşime hazır olma** betiğin yürümesini bekler. Sunucuda
oluşturma birinciyi öne çeker, ikinciyi olduğu yerde bırakır. Aradaki boşlukta kullanıcı içeriği
görür, tıklar ve tepki alamaz.

Boşluğun uzunluğu betiğin boyutuna ve aygıtın gücüne bağlıdır. Beşinci ders bu boşluğu
adlandırır ve daraltmanın yollarını ele alır.

#### Özet

- Sunucuda oluşturmada işaretleme isteğe yanıt verilirken üretilir; belge içeriği taşır ve
  ilk içeriğe kadar tek ağ turu yeter.
- Ölçüm, ilk bayt süresinin veri kaynağının işi kadar uzadığını, gövdenin büyüdüğünü ve
  ölçüm değerlerinin işaretlemede göründüğünü gösterir.
- Geri döngü arayüzünde yapılan bir ölçüm ağ turunu yalıtır; iki modelin farkı tur maliyeti
  arttıkça mutlak olarak büyür, oransal olarak küçülür.
- Bedel istek başınadır: hesaplama tekrarlanır, yanıtın önbelleklenebilirliği içeriğin
  değişkenliğiyle sınırlanır ve veri kaynağının gecikmesi doğrudan ilk bayta yazılır.
- Sunucudan gelen işaretleme etkileşimli değildir; ilk içerik boyası ile etkileşime hazır
  olma arasında betiğin yürümesini bekleyen bir aralık kalır.

#### Sonraki Adım

Sunucuda oluşturma, her istekte aynı işi yapmayı göze alır. İstasyonun tanıtım sayfası ile
geçmiş günlerin arşiv sayfaları için bu israftır: içerikleri isteğe göre değişmez, aynı
girdiden hep aynı işaretleme çıkar. Aynı çıktıyı üreten bir hesabın istek başına
tekrarlanması yerine bir kez yapılıp dosyaya yazılması mümkündür. Sonraki ders bu kaymayı
inceler: iş, isteğin geldiği andan derleme anına taşındığında ilk bayt süresine ne olur
ve bedel nereye — derleme süresine mi, tazeliğe mi — yazılır?

### Statik Site Üretimi

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/statik-site-uretimi

İşaretlemenin derleme zamanında üretilip dosyaya yazılması; aynı şablonun iki ayrı anda çağrılması, sayfa sayısıyla doğrusal büyüyen derleme süresi ve statik çıktının tazelik sınırı.

Sunucuda oluşturma, her istekte aynı işi yapmayı göze alır. İstasyonun arşiv sayfaları için bu iş
tümüyle tekrardır: bir günün ölçüm kayıtları o gün kapandıktan sonra değişmez ve aynı girdiden hep
aynı işaretleme çıkar. Aynı çıktıyı veren bir hesabı her istekte tekrarlamak yerine bir kez yapıp
sonucu saklamak mümkündür.

Bu ders o kaymayı yapar: iş, isteğin geldiği andan **derleme anına** taşınır. Ölçülecek
olan, ilk bayt süresine ne kazandırdığı ve bedelin nereye yazıldığıdır.

#### Şablon Aynı Kalır, Çağrıldığı An Değişir

Karşılaştırmanın anlamlı olması için iki yolun aynı şablonu kullanması gerekir. Şablon ve
veri kaynağı ayrı bir modüle konur; hem derleyici hem sunucu bunu çağırır.

```js
// sayfa.mjs — arsiv sayfasinin veri kaynagi ve sablonu; iki cagirani da bunu kullanir
export const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

// Gunluk olcum kaydi: gunden belirlenimci olarak turetilir, veri kaynagi taklididir.
export function gununOlcumleri(gun) {
  return Array.from({ length: 24 }, (_, saat) => ({
    zaman: String(saat).padStart(2, "0") + ":00",
    sicaklik: (-8 + ((gun * 7 + saat * 3) % 130) / 10).toFixed(1),
    nem: 40 + ((gun * 11 + saat * 5) % 55),
  }));
}

export function sayfa(gun, olcumler) {
  const satirlar = olcumler
    .map((o) => `<tr><td>${o.zaman}</td><td>${o.sicaklik}</td><td>${o.nem}</td></tr>`)
    .join("");
  return `<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac - gun ${gun}</title></head>
<body><h1>Kuzey Yamac Olcum Istasyonu</h1><h2>Gun ${gun}</h2>
<table><thead><tr><th>Saat</th><th>Sicaklik</th><th>Nem</th></tr></thead>
<tbody>${satirlar}</tbody></table></body></html>
`;
}
```

Statik üretimi bir teknik değil bir **çağırma anı** kararı yapan şey budur: kodda değişen
hiçbir şey yoktur, yalnızca `sayfa` işlevinin ne zaman çalıştırıldığı değişir.

#### Derleme Zamanında Üretim

Derleyici, üretilecek sayfaların listesini gezer ve her biri için şablonu çağırıp sonucu
diske yazar.

```js
// uret.mjs — arsiv sayfalarini derleme zamaninda uretir ve sureyi olcer
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { gununOlcumleri, sayfa } from "./sayfa.mjs";

const CIKTI = "cikti";

function derle(sayfaSayisi) {
  rmSync(CIKTI, { recursive: true, force: true });
  mkdirSync(CIKTI, { recursive: true });
  const basla = performance.now();
  let bayt = 0;
  for (let gun = 1; gun <= sayfaSayisi; gun++) {
    const govde = sayfa(gun, gununOlcumleri(gun));
    writeFileSync(`${CIKTI}/gun-${gun}.html`, govde);
    bayt += Buffer.byteLength(govde);
  }
  return { sure: performance.now() - basla, bayt };
}

derle(50); // isinma turu: olcume girmez

console.log("sayfa".padStart(7) + "sure (ms)".padStart(12) + "sayfa basina (ms)".padStart(19) +
  "cikti (KB)".padStart(13));
let oncekiSure = null, oncekiSayi = null;
for (const n of [100, 500, 1000, 5000]) {
  const { sure, bayt } = derle(n);
  let oran = "";
  if (oncekiSure !== null) {
    oran = `   sayfa x${(n / oncekiSayi).toFixed(0)} -> sure x${(sure / oncekiSure).toFixed(1)}`;
  }
  console.log(String(n).padStart(7) + sure.toFixed(1).padStart(12) +
    (sure / n).toFixed(3).padStart(19) + (bayt / 1024).toFixed(0).padStart(13) + oran);
  oncekiSure = sure; oncekiSayi = n;
}
```

`node uret.mjs` komutu bu dosyayla aynı dizinde çalıştırılır ve yanına bir `cikti` dizini
yazar.

```
  sayfa   sure (ms)  sayfa basina (ms)   cikti (KB)
    100         5.6              0.056          136
    500        26.0              0.052          680   sayfa x5 -> sure x4.6
   1000        48.9              0.049         1361   sayfa x2 -> sure x1.9
   5000       267.7              0.054         6813   sayfa x5 -> sure x5.5
```

Süreler makineye, disk hızına ve o andaki yüke bağlıdır; her çalıştırmada farklı çıkar.
Okunması gereken sütun sonuncusudur: sayfa sayısı beş katına çıktığında süre de yaklaşık
beş katına çıkar. Derleme **doğrusaldır** ve sayfa başına düşen maliyet sabit kalır.

Bu ölçekte sayfa başına maliyet şablonun işlenmesinden çok dosya yazmaya gider. Sayfa sayısı
büyüdükçe ölçülen oranın beşin biraz üstüne çıkması bunun belirtisidir: dizin girdileri
çoğaldıkça dosya sistemi işi ucuzlamaz.

#### İki Yol, Aynı Gövde

Aynı sayfa iki kaynaktan sunulup karşılaştırılabilir: diskteki dosya ve istek anında
çağrılan şablon.

```js
// statik-sunucu.mjs — ayni sayfayi iki yoldan sunar: diskteki dosya ve istek anindaki uretim
import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import { bekle, gununOlcumleri, sayfa } from "./sayfa.mjs";

const HTML = "text/html; charset=utf-8";

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const statik = istek.url.match(/^\/statik\/gun-(\d+)\.html$/);
  const anlik = istek.url.match(/^\/istek-aninda\/gun-(\d+)$/);
  let govde;
  if (statik) {
    govde = await readFile(`cikti/gun-${statik[1]}.html`, "utf8");
  } else if (anlik) {
    await bekle(40); // veri kaynagindan gunun kayitlarini okuma isi
    govde = sayfa(Number(anlik[1]), gununOlcumleri(Number(anlik[1])));
  } else {
    return yanit.writeHead(404).end();
  }
  yanit.setHeader("Content-Type", HTML);
  yanit.setHeader("Content-Length", Buffer.byteLength(govde));
  yanit.writeHead(200).end(govde);
}).listen(8173, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8173"));
```

```bash
#!/usr/bin/env bash
# Once sayfalari uretir, sonra iki yolu ayni olcuyle karsilastirir.
node uret.mjs > /dev/null   # sayfalari uretir, tablosu ayrica calistirilir
node statik-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1
curl -sS -o /dev/null http://127.0.0.1:8173/statik/gun-1.html   # isinma

BICIM='  ilk bayt %{time_starttransfer} s   govde %{size_download} B\n'

echo "--- diskteki dosya ---"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8173/statik/gun-7.html
echo "--- istek aninda uretim ---"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8173/istek-aninda/gun-7

echo "--- iki govde ayni mi? ---"
curl -sS http://127.0.0.1:8173/statik/gun-7.html > a.html
curl -sS http://127.0.0.1:8173/istek-aninda/gun-7 > b.html
if cmp -s a.html b.html; then echo "ayni"; else echo "farkli"; fi
rm -f a.html b.html

kill "$sunucu"
```

```
--- diskteki dosya ---
  ilk bayt 0.000912 s   govde 1386 B
--- istek aninda uretim ---
  ilk bayt 0.042926 s   govde 1386 B
--- iki govde ayni mi? ---
ayni
```

Gövdeler bayt bayt aynıdır; ilk bayt süreleri arasında yaklaşık kırk yedi kat fark vardır — 0,0009
saniyeye karşı 0,0429 saniye. Kullanıcıya giden şey değişmemiştir; o şeyi üretme işi zamanda
geriye alınmıştır.

Farkın kaynağı, veri kaynağının 40 milisaniyelik işidir. Statik yolda bu iş derleme
sırasında bir kez yapılmıştır ve istek anında hiç yapılmaz. İstek anındaki üretimde ise her
istekte yeniden yapılır: bin ziyaretçi, bin kez aynı hesap demektir.

#### Statik Çıktının Getirdikleri

Diskteki bir dosya olmanın, dosya olmaktan doğan sonuçları vardır.

**Sunucu çalıştırma zamanı gerektirmez.** Yanıt bir dosya okumasıdır; bir uygulama süreci,
bir veri kaynağı bağlantısı ya da bir çalışma zamanı ortamı olmadan verilebilir. Bu, bir
içerik dağıtım ağının çıktıyı olduğu gibi kopyalayıp sunabilmesi anlamına gelir.

**Yanıt herkes için aynıdır.** Aynı adres her istemciye aynı baytları verir; bu, yanıtın
paylaşımlı katmanlarda uzun ömürle saklanabilmesini sağlar. Önbellek Stratejileri dersindeki
ayrım burada en uygun tarafındadır: değişmeyen içerik, doğrulama isteği bile gerektirmeyen
içeriktir.

**Yük, istek hacminden bağımsızlaşır.** Sunucuda oluşturmada maliyet istek sayısıyla
büyürdü; statik üretimde maliyet **sayfa sayısıyla** büyür ve bir kez ödenir. On ziyaretçi
ile on bin ziyaretçi arasındaki fark, yalnızca bant genişliğidir.

**Hata yüzeyi daralır.** İstek anında çalışan kod yoksa, istek anında oluşacak hata da
yoktur. Bir şablon hatası derleme sırasında görünür ve yayına çıkmadan yakalanır.

#### Nerede Uygulanamaz

Statik üretimin koşulu tektir: sayfanın çıktısı yalnızca **derleme anında bilinen
girdilere** bağlı olmalıdır. Bu koşul üç durumda bozulur.

Sayfa kullanıcıya göre değişiyorsa üretilemez. İstasyon sorumlusunun paneli her kişi için
farklıdır; derleme zamanında hangi kişi için üretileceği bilinmez.

İçerik derlemeler arasında değişiyorsa çıktı eskir. Canlı ölçüm listesi dakikada bir
değişir; günde bir kez derlenen bir dosya, gösterdiği değerin yaşını hiçbir zaman
bildiremez. Statik üretimde tazelik, derleme sıklığıyla sınırlıdır.

Sayfa sayısı derlemeyi taşıyamaz hâle geliyorsa yöntem tıkanır. Ölçülen doğrusallık iyi
haber gibi görünür ama doğrusal büyümenin sonu vardır: sayfa başına 0,05 milisaniye, yüz bin
sayfada beş saniye, bir milyon sayfada bir dakikanın üstü demektir. Gerçek bir derlemede
sayfa başına iş bu örnekten büyüktür ve tek bir yazım düzeltmesi için bütün sitenin yeniden
üretilmesi kabul edilemez hâle gelir.

Bu üçüncü sınır, statik üretimin kendi içinden çıkan bir soruya yol açar: bütün sayfaları
yeniden üretmeden yalnızca değişenleri tazelemek mümkün müdür?

#### Özet

- Statik üretim, işaretlemeyi üreten şablonu istek anında değil derleme anında çağırır;
  şablonun kendisi değişmez.
- Ölçüm iki yolun gövdesinin bayt bayt aynı olduğunu, ilk bayt sürelerinin ise yaklaşık kırk yedi
  kat ayrıldığını gösterir; fark, veri kaynağının işinin zamanda öne alınmasıdır.
- Derleme süresi sayfa sayısıyla doğrusal büyür ve sayfa başına maliyet sabit kalır;
  toplam maliyet istek hacmine değil sayfa sayısına bağlıdır.
- Statik çıktı çalışma zamanı gerektirmez, herkese aynı yanıtı verir ve uzun ömürle
  önbelleklenebilir; istek anında çalışan kod olmadığı için hata yüzeyi daralır.
- Yöntem, çıktı derleme anında bilinmeyen girdilere bağlıysa, içerik derlemeler arasında
  değişiyorsa ya da sayfa sayısı derlemeyi taşıyamıyorsa uygulanamaz.

#### Sonraki Adım

Üçüncü sınır, ikisinin arasında bir yer olduğunu düşündürür. Bir sayfa statik olarak üretilmiş
olabilir ama sonsuza kadar geçerli sayılmak zorunda değildir: kendisine bir geçerlilik süresi
tanınabilir ve süre dolduğunda bir sonraki istek eski kopyayı alırken arka planda yeni sürümü
üretilebilir. Böylece hem çalışma zamanı maliyeti düşük kalır hem de tazelik derleme sıklığına
mahkûm olmaz. Sonraki ders bu düzeneği kurar ve ölçer: tazelik penceresi, arka plan tazeleme
kuyruğu, isabet ve ıska sayıları.

### Artımlı Yeniden Üretim

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/artimli-yeniden-uretim

Statik çıktının seçici tazelenmesi; tazelik penceresi, arka plan tazeleme kuyruğu, ıska–isabet sayımı ve pencere uzunluğunun üretim yükü ile içerik gecikmesi arasında kurduğu değiş tokuş.

Statik üretimin üç sınırından biri, tazeliğin derleme sıklığına bağlanmasıydı. Bir sayfa derleme
anında üretilmiş olabilir ama sonsuza kadar geçerli sayılmak zorunda değildir: kendisine bir
geçerlilik süresi tanınabilir ve süre dolduğunda bir sonraki istek eski kopyayı alırken arka
planda yeni sürümü hazırlanabilir.

Bu düzenek Önbellek Stratejileri dersinde tarayıcı tarafında görülen yeniden doğrulayarak
sunma davranışının sunucu tarafındaki karşılığıdır. Bu ders onu bir oluşturma modeli olarak
kurar ve iki büyüklüğü ölçer: kaç isteğin ne kadar eski içerik gördüğü ve kaç yeniden
üretim işi doğduğu.

#### Üç Yanıt Durumu

Bir sayfa isteği geldiğinde depoya bakılır ve üç durumdan biri gerçekleşir.

**Iska**: depoda o sayfanın hiçbir kopyası yoktur. İstek, üretimin bitmesini bekler ve
sunucuda oluşturmanın maliyetini ödemiş olur. Bu durum her sayfa için en çok bir kez
yaşanır.

**Taze isabet**: depodaki kopya, tazelik penceresi içinde üretilmiştir. Doğrudan sunulur;
hiçbir üretim işi doğmaz.

**Eski sunum**: kopya vardır ama penceresi dolmuştur. Kopya olduğu gibi sunulur ve yeniden üretim
arka plan kuyruğuna alınır. İstek bekletilmez; tazelenme bir sonraki isteklere yansır.

Bu üç durumun bir sunucuda nasıl göründüğü doğrudan gözlenebilir.

```js
// artimli-sunucu.mjs — tazelik penceresi ve arka plan tazelemesi olan sunucu
import { createServer } from "node:http";

const PENCERE = 3000;   // ms, kopyanin taze sayildigi sure
const URETIM = 1000;    // ms, sayfanin uretilme maliyeti

let kaynakSurum = 0;
setInterval(() => { kaynakSurum += 1; }, 1000).unref(); // veri her saniye degisir

let kayit = null;       // { govde, surum, uretim }
let tazeleniyor = false;

const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

async function uret() {
  await bekle(URETIM);
  return { govde: `olcum listesi surum ${kaynakSurum}\n`, surum: kaynakSurum, uretim: Date.now() };
}

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  if (istek.url !== "/olcumler") return yanit.writeHead(404).end();

  let durum;
  if (!kayit) {
    durum = "iska";                       // kopya yok: istek uretimi bekler
    kayit = await uret();
  } else if (Date.now() - kayit.uretim < PENCERE) {
    durum = "taze";                       // pencere icinde: dogrudan sunulur
  } else {
    durum = "eski";                       // pencere disi: eski kopya sunulur
    if (!tazeleniyor) {                   // tazeleme bir kez kuyruga alinir
      tazeleniyor = true;
      uret().then((yeni) => { kayit = yeni; tazeleniyor = false; });
    }
  }
  yanit.setHeader("X-Durum", durum);
  yanit.setHeader("Content-Type", "text/plain; charset=utf-8");
  yanit.setHeader("Content-Length", Buffer.byteLength(kayit.govde));
  yanit.writeHead(200).end(kayit.govde);
}).listen(8174, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8174"));
```

Aşağıdaki betik bu dosyayla aynı dizinde çalıştırılır. Bağlantı noktası 8174 keyfîdir ve
boş olmalıdır; kullanımdaysa iki dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# artimli-sunucu.mjs'yi baslatir, dort ayri anda istek yapar, sonra durdurur.
node artimli-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

iste() {
  printf '%-34s' "$1"
  curl -sS -D basliklar -w '   sure %{time_total} s   ' http://127.0.0.1:8174/olcumler | tr -d '\n'
  grep -i '^x-durum' basliklar | tr -d '\r'
}

iste "1) ilk istek"
iste "2) hemen ardindan"
sleep 4
iste "3) pencere dolduktan sonra"
sleep 2
iste "4) tazeleme bittikten sonra"
rm -f basliklar

kill "$sunucu"
```

```
1) ilk istek                      olcum listesi surum 1   sure 1.007820 s   X-Durum: iska
2) hemen ardindan                 olcum listesi surum 1   sure 0.000844 s   X-Durum: taze
3) pencere dolduktan sonra        olcum listesi surum 1   sure 0.000695 s   X-Durum: eski
4) tazeleme bittikten sonra       olcum listesi surum 7   sure 0.001069 s   X-Durum: taze
```

Süre alanları makineye bağlıdır. Belirleyici olan oran şudur: ıska yaklaşık bir saniye
sürerken diğer üç istek milisaniyenin altında kalır — aradaki fark yaklaşık bin kattır.
Üçüncü istek penceresi dolmuş bir kopyayı sunar ve yine hızlıdır; bekleyen yalnızca
tazeliktir, kullanıcı değil.

Dördüncü satır düzeneğin karakteristik davranışını gösterir: sunulan sürüm 1'den 7'ye
atlamıştır. Arka plandaki üretim, başladığı andaki değil **bittiği andaki** kaynağı
yakalar; bu arada kaynak birkaç sürüm ilerlemiştir.

#### Tazelik Penceresi Gerçek Yaşı Söylemez

Yanıt üzerindeki "taze" damgası, kopyanın kaynağıyla aynı olduğunu değil, **pencere içinde
üretildiğini** bildirir. İkisi aynı şey değildir. Aşağıdaki benzetim bu ayrımı sanal bir
saat üzerinde açığa çıkarır; çıktı makineden bağımsızdır.

```js
// artimli.mjs — tazelik penceresi ve arka plan tazeleme kuyrugu modeli
// Sanal saat kullanilir: cikti belirlenimcidir, makineye bagli degildir.

const PENCERE_VARSAYILAN = 60;   // saniye
const URETIM_SURESI = 2;         // saniye, arka planda bir sayfanin yeniden uretilmesi
const SURE = 1800;               // saniye, benzetimin uzunlugu
const ISTEK_SAYISI = 300;

// Kaynak verinin surumu: olcum listesi her 60 saniyede bir degisir, arsiv hic degismez.
const kaynakSurum = (yol, t) => (yol === "/olcumler" ? Math.floor(t / 60) : 0);

// Belirlenimci sozde rastgele dizi (dogrusal esleskli uretec).
function uretec(tohum) {
  let x = tohum;
  return () => (x = (x * 1103515245 + 12345) % 2147483648) / 2147483648;
}

// Istek akisi: yollar agirliklarina gore secilir, zamanlar artan sirada uretilir.
function istekAkisi() {
  const rastgele = uretec(20260728);
  const agirlik = [["/olcumler", 6], ["/gun-1", 2], ["/gun-2", 1], ["/gun-3", 1]];
  const havuz = agirlik.flatMap(([yol, n]) => Array(n).fill(yol));
  const istekler = [];
  for (let i = 0; i < ISTEK_SAYISI; i++) {
    istekler.push({
      t: Math.floor(rastgele() * SURE),
      yol: havuz[Math.floor(rastgele() * havuz.length)],
    });
  }
  return istekler.sort((a, b) => a.t - b.t);
}

function calistir(pencere, gunlukTut) {
  const depo = new Map();      // yol -> { surum, uretim }
  const kuyruk = new Map();    // yol -> tamamlanma zamani
  const sayac = { taze: 0, eski: 0, iska: 0, uretim: 0 };
  const gecikmeler = [];
  const gunluk = [];

  for (const { t, yol } of istekAkisi()) {
    // Kuyruktaki tazeleme suresi dolduysa depoya yazilir.
    const biter = kuyruk.get(yol);
    if (biter !== undefined && biter <= t) {
      depo.set(yol, { surum: kaynakSurum(yol, biter), uretim: biter });
      kuyruk.delete(yol);
      sayac.uretim += 1;
    }

    const kayit = depo.get(yol);
    let sonuc;
    if (!kayit) {
      // Iska: kopya yok, istek uretimi bekler.
      depo.set(yol, { surum: kaynakSurum(yol, t), uretim: t });
      sayac.iska += 1; sayac.uretim += 1;
      sonuc = "iska";
    } else if (t - kayit.uretim < pencere) {
      sayac.taze += 1;
      sonuc = "taze";
    } else {
      // Eski kopya sunulur, tazeleme kuyruga alinir (zaten kuyruktaysa yenisi eklenmez).
      sayac.eski += 1;
      if (!kuyruk.has(yol)) kuyruk.set(yol, t + URETIM_SURESI);
      sonuc = "eski";
    }

    const sunulan = depo.get(yol);
    const gecikme = kaynakSurum(yol, t) - sunulan.surum;
    gecikmeler.push(gecikme);
    if (gunlukTut && yol === "/olcumler" && gunluk.length < 14) {
      gunluk.push(`${String(t).padStart(4)}s  ${sonuc.padEnd(7)}` +
        `sunulan surum ${String(sunulan.surum).padStart(2)}   kaynak surum ` +
        `${String(kaynakSurum(yol, t)).padStart(2)}   gecikme ${gecikme}` +
        (kuyruk.has(yol) ? "   [kuyrukta]" : ""));
    }
  }
  const ortalama = gecikmeler.reduce((a, b) => a + b, 0) / gecikmeler.length;
  return { sayac, ortalama, enKotu: Math.max(...gecikmeler), gunluk };
}

const ilk = calistir(PENCERE_VARSAYILAN, true);
console.log(`--- /olcumler istekleri, ilk 14 (pencere ${PENCERE_VARSAYILAN} s) ---`);
for (const satir of ilk.gunluk) console.log(satir);

console.log(`\n--- ${ISTEK_SAYISI} istek, ${SURE} s, pencere degistikce ---`);
console.log("pencere".padStart(8) + "taze".padStart(7) + "eski".padStart(7) + "iska".padStart(7) +
  "uretim".padStart(9) + "ort. gecikme".padStart(14) + "en kotu".padStart(9));
for (const pencere of [10, 60, 300, 1800]) {
  const { sayac, ortalama, enKotu } = calistir(pencere, false);
  console.log(`${pencere} s`.padStart(8) + String(sayac.taze).padStart(7) +
    String(sayac.eski).padStart(7) + String(sayac.iska).padStart(7) +
    String(sayac.uretim).padStart(9) + ortalama.toFixed(2).padStart(14) +
    String(enKotu).padStart(9));
}
```

```
--- /olcumler istekleri, ilk 14 (pencere 60 s) ---
   5s  iska   sunulan surum  0   kaynak surum  0   gecikme 0
   9s  taze   sunulan surum  0   kaynak surum  0   gecikme 0
  20s  taze   sunulan surum  0   kaynak surum  0   gecikme 0
  28s  taze   sunulan surum  0   kaynak surum  0   gecikme 0
  37s  taze   sunulan surum  0   kaynak surum  0   gecikme 0
  90s  eski   sunulan surum  0   kaynak surum  1   gecikme 1   [kuyrukta]
  92s  taze   sunulan surum  1   kaynak surum  1   gecikme 0
  92s  taze   sunulan surum  1   kaynak surum  1   gecikme 0
  98s  taze   sunulan surum  1   kaynak surum  1   gecikme 0
 107s  taze   sunulan surum  1   kaynak surum  1   gecikme 0
 116s  taze   sunulan surum  1   kaynak surum  1   gecikme 0
 117s  taze   sunulan surum  1   kaynak surum  1   gecikme 0
 146s  taze   sunulan surum  1   kaynak surum  2   gecikme 1
 154s  eski   sunulan surum  1   kaynak surum  2   gecikme 1   [kuyrukta]

--- 300 istek, 1800 s, pencere degistikce ---
 pencere   taze   eski   iska   uretim  ort. gecikme  en kotu
    10 s     98    198      4      179          0.14        1
    60 s    216     80      4       73          0.38        2
   300 s    276     20      4       23          1.42        5
  1800 s    296      0      4        4          9.38       29
```

Günlüğün 146. saniyedeki satırı dersin can alıcı noktasıdır: yanıt **taze** damgalıdır ama gecikme
birdir. Kopya 92. saniyede üretilmiştir; 60 saniyelik pencere 152. saniyeye kadar sürer, oysa
kaynak 120. saniyede bir sürüm ilerlemiştir. Pencere, kopyanın yaşını ölçer, içeriğin doğruluğunu
değil.

Doksanıncı saniyedeki satır kuyruğun işleyişini gösterir: eski kopya sunulmuş, tazeleme kuyruğa
alınmış ve iki saniye sonra tamamlandığı için 92. saniyedeki istek yeni sürümü görmüştür.

#### Pencere Uzunluğu Bir Değiş Tokuştur

Alt tablo pencere uzunluğunun iki büyüklüğü ters yönde çektiğini gösterir. Pencere onda
bir saniyeye indirildiğinde ortalama gecikme 0,14 sürüme düşer ama üretim sayısı 179'a
çıkar. Pencere otuz dakikaya çıkarıldığında üretim dörde iner — her sayfa için yalnızca
ilk ıska — ama ortalama gecikme 9,38 sürüme, en kötü durum 29 sürüme fırlar.

Aradaki ilişki doğrusal değildir. Pencereyi 10 saniyeden 60 saniyeye çıkarmak üretimi
2,5 kat azaltırken ortalama gecikmeyi yalnızca 2,7 kat artırır; 300 saniyeden 1800 saniyeye
çıkarmak ise üretimi 5,8 kat azaltırken gecikmeyi 6,6 kat artırır. Kazanç eğrisi düzleşir:
belirli bir noktadan sonra pencereyi uzatmak, tasarruf ettiğinden fazlasını tazelikten alır.

Iska sayısının bütün satırlarda dört olması da anlamlıdır: dört ayrı yol vardır ve her yol
için ıska yalnızca bir kez yaşanır. Bekleyen istek sayısı, sayfa sayısına bağlıdır, istek
hacmine değil.

#### Uygulamada Dikkat Edilen Noktalar

**Kuyruk tekilleştirilir.** Modelde bir yol kuyruktayken ikinci bir tazeleme eklenmez.
Bu koruma olmasaydı, penceresi dolmuş popüler bir sayfaya gelen yüz eşzamanlı istek yüz
üretim işi doğururdu. Aynı anda çok sayıda kopyanın penceresi dolarsa üretim yükü ani bir
sıçrama yapar; pencerelere küçük bir rastgele pay eklemek bu sıçramayı yayar.

**İlk ıska öngörülebilir kılınır.** Sayfa ilk kez istendiğinde tam üretim maliyeti ödenir.
Trafiği bilinen sayfalar derleme sırasında önceden üretilerek bu maliyet yayından önce
alınır; kalanlar ilk istekte doğar.

**Geçersiz kılma penceresi tamamlar.** Pencere zamana dayalı bir tahmindir. İçeriğin
değiştiği kesin olarak biliniyorsa — bir ölçüm düzeltildiğinde, bir sayfa yayından
kaldırıldığında — ilgili kaydın doğrudan geçersiz kılınması beklemeyi ortadan kaldırır.
Zaman tabanlı pencere ile olay tabanlı geçersiz kılma birlikte kullanılır.

**Eski içerik arayüzde gizlenmez.** Kullanıcı bir ölçüm listesine bakarken gördüğü
değerin ne zaman üretildiğini bilmelidir. Üretim zamanının sayfada yazılması, gecikmeyi
ortadan kaldırmaz ama yanıltıcı olmaktan çıkarır.

#### Özet

- Artımlı yeniden üretimde bir istek üç durumdan birine düşer: ıska üretimi bekletir, taze isabet
  doğrudan sunulur ve eski sunum kopyayı verip tazelemeyi kuyruğa alır.
- Ölçüm, ıskanın üretim maliyetini ödediğini, diğer iki durumun ise binde bir mertebesinde
  sürdüğünü gösterir; bekleyen tazeliktir, kullanıcı değil.
- Tazelik penceresi kopyanın yaşını ölçer, kaynağa uygunluğunu değil; pencere içinde
  üretilmiş bir kopya kaynağın gerisinde olabilir.
- Pencereyi kısaltmak gecikmeyi azaltır ve üretim yükünü artırır; kazanç eğrisi düzleştiği
  için belirli bir uzunluktan sonra tasarruf tazelikten daha pahalıya alınır.
- Iska sayısı sayfa sayısına bağlıdır, istek hacmine değil; kuyruğun tekilleştirilmesi
  eşzamanlı isteklerin üretim yükünü çoğaltmasını önler.

#### Sonraki Adım

Buraya kadarki dört model, işaretlemenin **nerede** üretileceğini tartıştı ve hepsinde bir
şey ortak kaldı: yanıt tek parçada üretilir, tek parçada gönderilir ve istemciye ulaştığında
etkileşimsizdir. Sunucuda oluşturma dersinde ölçülen boşluk — içeriğin göründüğü an ile
düğmenin çalıştığı an arasındaki aralık — hâlâ yerinde durur. Sonraki ders bu aralığı
adlandırır ve iki yönden daraltır: yanıtın parça parça gönderilmesi ve etkileşimin sayfanın
tamamına değil, gerçekten gereken bölümlerine verilmesi.

### Susuzlaştırma ve Akış

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/susuzlastirma-ve-akis

Sunucuda üretilmiş işaretlemenin istemcide etkileşim kazanması; canlandırmanın maliyeti, parçalı ve gecikmeli biçimleri, parçalı gönderimle ilk baytın öne alınması ve iki tekniğin birlikte kurduğu denge.

Sunucuda oluşturma dersinde ölçülen bir boşluk açık kalmıştı: gövde ekrana geldiği an ile
düğmenin yanıt verdiği an aynı değildir. İşaretleme sunucuda üretilmiştir, olay
dinleyicileri ise betik yürüdükten sonra bağlanır. Bu ders o aralığı adlandırır ve iki
yönden daraltır.

Aralığı kapatan işleme bu kursta **canlandırma (hydration)** denir: sunucudan gelen durağan
işaretlemenin, istemcide çalışan kodla eşleştirilip etkileşimli hâle getirilmesi. Dersin
katalogdaki adı çeviri geleneğinden gelir; kavram kurs boyunca tek bir karşılıkla,
canlandırma olarak anılır. İkinci teknik, yanıtın tek parçada değil parça parça
gönderilmesidir; buna **akışlı gönderim** denir.

#### Canlandırma Ne Yapar

Sunucudan gelen belgede ağaç zaten kuruludur. İstemcideki kodun işi ağacı yeniden
kurmak değil, kendi bileşen ağacını gelen ağaçla **eşleştirmektir**: hangi düğümün hangi
bileşene karşılık geldiğini bulmak, olay dinleyicilerini bağlamak ve durum nesnelerini
kurmak.

Bu eşleştirmenin üç maliyeti vardır. Bileşenlerin kodu indirilmelidir. İndirilen kod
ayrıştırılıp yürütülmelidir. Ağaçtaki her düğüm için eşleşme yapılmalı ve dinleyiciler
bağlanmalıdır. Üçüncü kalem, sayfa büyüdükçe düğüm sayısıyla doğrusal büyür.

Eşleştirmenin bir de doğruluk koşulu vardır: istemcinin ürettiği ağaç, sunucununkiyle aynı
olmalıdır. Sunucuda o anki saate göre bir değer yazılmışsa ve istemci başka bir saat
görüyorsa iki ağaç ayrışır. Bu ayrışma sessiz kalmaz; eşleşemeyen bölüm ya baştan üretilir
ya da tutarsız bir arayüz bırakır. Bu yüzden canlandırılacak işaretlemenin **belirlenimci**
olması gerekir: aynı girdiden iki tarafta da aynı çıktı çıkmalıdır.

#### Yanıtın Parça Parça Gönderilmesi

Canlandırma başlamadan önce belgenin gelmesi gerekir. Belge tek parçada üretiliyorsa en
yavaş veri kaynağı bütün sayfayı bekletir. Parçalı gönderim bu bağı koparır: sunucu, hazır
olan bölümü hazır olduğu anda yollar.

```js
// akis-sunucu.mjs — ayni sayfayi iki gonderim bicimiyle veren sunucu
import { createServer } from "node:http";

const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

// Sayfanin uc bolumu; her biri ayri bir veri kaynagindan gelir ve 200 ms surer.
const BOLUMLER = [
  ["baslik", '<h1>Kuzey Yamac Olcum Istasyonu</h1>'],
  ["olcumler", '<ul><li>06:00 -4.2 C</li><li>07:00 -3.8 C</li><li>08:00 -2.1 C</li></ul>'],
  ["yorum", '<p>Son 24 saatte donma noktasinin altinda 19 olcum var.</p>'],
];
const oku = async ([, govde]) => { await bekle(200); return govde; };

const BAS = `<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac</title></head><body>`;
const SON = `</body></html>\n`;

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  if (istek.url === "/tek-parca") {
    const parcalar = [];
    for (const bolum of BOLUMLER) parcalar.push(await oku(bolum));
    const govde = BAS + parcalar.join("") + SON;
    yanit.setHeader("Content-Type", "text/html; charset=utf-8");
    yanit.setHeader("Content-Length", Buffer.byteLength(govde));
    yanit.writeHead(200).end(govde);
  } else if (istek.url === "/akisli") {
    yanit.setHeader("Content-Type", "text/html; charset=utf-8");
    yanit.writeHead(200);            // Content-Length yazilmaz: parcali gonderim baslar
    yanit.write(BAS);                // kabuk beklemeden yola cikar
    for (const bolum of BOLUMLER) yanit.write(await oku(bolum));
    yanit.end(SON);
  } else {
    yanit.writeHead(404).end();
  }
}).listen(8175, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8175"));
```

Parçaların varış anlarını görmek için bir okuyucuya gerek vardır; `curl` yalnızca ilk ve
son anı bildirir.

```js
// okuyucu.mjs — bir yanitin parcalarinin varis anlarini yazar
// Kullanim: node okuyucu.mjs /akisli
import { get } from "node:http";

const yol = process.argv[2] ?? "/akisli";
const basla = performance.now();
const gecen = () => (performance.now() - basla).toFixed(0).padStart(4);

get({ host: "127.0.0.1", port: 8175, path: yol }, (yanit) => {
  console.log(`${gecen()} ms  yanit basligi   Transfer-Encoding: ` +
    `${yanit.headers["transfer-encoding"] ?? "(yok)"}   ` +
    `Content-Length: ${yanit.headers["content-length"] ?? "(yok)"}`);
  yanit.on("data", (parca) => {
    const onizleme = parca.toString().replace(/\n/g, " ").slice(0, 46);
    console.log(`${gecen()} ms  parca ${String(parca.length).padStart(3)} B   ${onizleme}`);
  });
  yanit.on("end", () => console.log(`${gecen()} ms  yanit bitti`));
});
```

Aşağıdaki betik bu iki dosyayla aynı dizinde çalıştırılır. Bağlantı noktası 8175 keyfîdir
ve boş olmalıdır; kullanımdaysa üç dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# akis-sunucu.mjs'yi baslatir, iki gonderim bicimini karsilastirir, sonra durdurur.
node akis-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

echo "--- ilk bayt sureleri (curl) ---"
for yol in /tek-parca /akisli; do
  printf '  %-11s ' "$yol"
  curl -sS -o /dev/null -w 'ilk bayt %{time_starttransfer} s   toplam %{time_total} s\n' \
    "http://127.0.0.1:8175$yol"
done

echo "--- /tek-parca: parcalarin varisi ---"
node okuyucu.mjs /tek-parca
echo "--- /akisli: parcalarin varisi ---"
node okuyucu.mjs /akisli

kill "$sunucu"
```

```
--- ilk bayt sureleri (curl) ---
  /tek-parca  ilk bayt 0.611718 s   toplam 0.611916 s
  /akisli     ilk bayt 0.001006 s   toplam 0.606051 s
--- /tek-parca: parcalarin varisi ---
 611 ms  yanit basligi   Transfer-Encoding: (yok)   Content-Length: 281
 613 ms  parca 281 B   <!doctype html> <html lang="tr"><head><meta ch
 613 ms  yanit bitti
--- /akisli: parcalarin varisi ---
   3 ms  yanit basligi   Transfer-Encoding: chunked   Content-Length: (yok)
   4 ms  parca  99 B   <!doctype html> <html lang="tr"><head><meta ch
 204 ms  parca  36 B   <h1>Kuzey Yamac Olcum Istasyonu</h1>
 407 ms  parca  72 B   <ul><li>06:00 -4.2 C</li><li>07:00 -3.8 C</li>
 610 ms  parca  59 B   <p>Son 24 saatte donma noktasinin altinda 19 o
 610 ms  parca  15 B   </body></html> 
 612 ms  yanit bitti
```

Süre alanları makineye bağlıdır; her çalıştırmada farklı çıkar. Yorumu iki oran taşır.

**İlk bayt süresi yaklaşık altı yüz kat kısalmıştır.** Tek parçalı yanıtta ilk bayt üç
bölümün tamamını bekler; akışlı yanıtta kabuk, hiçbir veri kaynağı sorulmadan yola çıkar.
**Toplam süre değişmemiştir**: iki yanıt da yaklaşık 610 milisaniyede biter. Akış, işi
azaltmaz; işin sonucunu **beklemeden** göndermeye başlar.

Başlıklardaki fark bu davranışın taşıyıcı katmandaki karşılığıdır. Gövdenin uzunluğu
gönderim başlarken bilinmediği için `Content-Length` yazılamaz; taşıma
`Transfer-Encoding: chunked` ile parçalı yapılır ve her parça kendi uzunluğunu bildirir.

Kazanç ekranda da karşılığını bulur, çünkü belge ayrıştırıcısı gelen baytları beklemeden
işler: kabuk geldiğinde biçem dosyaları istenebilir, başlık geldiğinde boyanabilir. İçerik
sırayla belirir.

#### Akışın Getirdiği Kısıtlar

Yanıtın başlığı ilk parçayla birlikte gider ve bir daha değiştirilemez. Bunun üç sonucu
vardır.

**Durum kodu geri alınamaz.** Üçüncü bölümün verisi alınamazsa 500 döndürülemez; başlık
çoktan 200 olarak gönderilmiştir. Hata, gövdenin içinde bir bölüm olarak anlatılmak
zorundadır. Bu yüzden akışlı bir yanıtta her bölümün kendi hata işaretlemesi tanımlanır.

**Yönlendirme yapılamaz.** Kimlik denetimi gibi yanıtın tamamını iptal edebilecek kararlar,
ilk bayt gönderilmeden önce verilmelidir.

**Sıra bir tasarım kararı hâline gelir.** Bölümler belgede yazıldıkları sırada gider. En
yavaş bölüm başa konursa akışın kazancı yok olur. Bu yüzden bölümler hızlarına göre değil,
kullanıcının önce görmesi gereken şeye göre sıralanır ve yavaş bölümlerin yerine önce bir
yükleme göstergesi yazılır.

#### Canlandırmanın Bölünmesi

Akış belgeyi öne çeker; canlandırma maliyeti yerinde durur. Onu azaltmanın yolu, sayfanın
tamamını canlandırmamaktır.

**Parçalı canlandırma**, yalnızca etkileşimli bileşenlerin canlandırılmasıdır. Ölçüm
tablosu bir metin bloğudur; hiçbir olayı dinlemez, hiçbir durumu tutmaz. Böyle bir bölümün
canlandırılması boşa harcanan iştir.

**Gecikmeli canlandırma**, etkileşimli bileşenleri de hemen canlandırmamaktır. İlk ekranın
dışında kalan bir grafik, görünür alana girdiğinde ya da ilk etkileşimde canlandırılabilir.
Gözlemciler dersinde tanıtılan kesişim gözlemcisi, bu tetiklemenin doğal aracıdır.

```js
// canlandirma.mjs — ayni sayfanin uc canlandirma bicimindeki ilk yuk maliyeti
// Girdiler varsayimdir; cikti bu varsayimlarin aritmetigidir, olcum degildir.

const CEKIRDEK_KB = 25;      // her bicimde gonderilen ortak calisma zamani
const AYRISTIRMA_MS_KB = 0.35;
const DUGUM_MS = 0.02;       // bir dugumu etkilesime baglama maliyeti

// Istasyon sayfasinin bilesen dokumu.
const BILESENLER = [
  { ad: "sayfa kabugu",   dugum: 120, etkilesimli: false, kb: 4,  ilkEkran: true },
  { ad: "gezinme menusu", dugum: 40,  etkilesimli: true,  kb: 6,  ilkEkran: true },
  { ad: "olcum tablosu",  dugum: 520, etkilesimli: false, kb: 9,  ilkEkran: true },
  { ad: "suzgec cubugu",  dugum: 30,  etkilesimli: true,  kb: 14, ilkEkran: true },
  { ad: "grafik",         dugum: 260, etkilesimli: true,  kb: 48, ilkEkran: false },
  { ad: "yorum kutusu",   dugum: 45,  etkilesimli: true,  kb: 12, ilkEkran: false },
  { ad: "dipnotlar",      dugum: 90,  etkilesimli: false, kb: 3,  ilkEkran: true },
];

const topla = (liste, alan) => liste.reduce((t, b) => t + b[alan], 0);
const TUM_KB = topla(BILESENLER, "kb");
const ETKILESIMLI = BILESENLER.filter((b) => b.etkilesimli);

// Uc bicim: ilk yukte hangi bilesenlerin canlandirildigi ve toplamda hangilerinin
// kodunun gerektigi degisir.
const BICIMLER = [
  ["tam canlandirma", () => true, BILESENLER],
  ["parcali canlandirma", (b) => b.etkilesimli, ETKILESIMLI],
  ["gecikmeli canlandirma", (b) => b.etkilesimli && b.ilkEkran, ETKILESIMLI],
];

console.log("bicim".padEnd(24) + "ilk yuk KB".padStart(12) + "dugum".padStart(8) +
  "ilk yuk ms".padStart(12) + "sonradan KB".padStart(13) + "hic gonderilmeyen".padStart(19));
for (const [ad, secili, gerekli] of BICIMLER) {
  const canlanan = BILESENLER.filter(secili);
  const ilkKb = CEKIRDEK_KB + topla(canlanan, "kb");
  const dugum = topla(canlanan, "dugum");
  const sure = ilkKb * AYRISTIRMA_MS_KB + dugum * DUGUM_MS;
  const sonradan = topla(gerekli, "kb") - topla(canlanan, "kb");
  const hic = TUM_KB - topla(gerekli, "kb");
  console.log(ad.padEnd(24) + ilkKb.toFixed(0).padStart(12) + String(dugum).padStart(8) +
    sure.toFixed(1).padStart(12) + sonradan.toFixed(0).padStart(13) +
    hic.toFixed(0).padStart(19));
}

const tumDugum = topla(BILESENLER, "dugum");
const etkilesimliDugum = topla(ETKILESIMLI, "dugum");
console.log(`\ntoplam dugum ${tumDugum}, etkilesimli dugum ${etkilesimliDugum} ` +
  `(%${((etkilesimliDugum / tumDugum) * 100).toFixed(0)})`);
```

```
bicim                     ilk yuk KB   dugum  ilk yuk ms  sonradan KB  hic gonderilmeyen
tam canlandirma                  121    1105        64.4            0                  0
parcali canlandirma              105     375        44.3            0                 16
gecikmeli canlandirma             45      70        17.1           60                 16

toplam dugum 1105, etkilesimli dugum 375 (%34)
```

Son satır kararın dayanağıdır: sayfadaki düğümlerin yalnızca yüzde 34'ü etkileşimlidir.
Tam canlandırma, kalan yüzde 66 için de eşleşme yapar ve o bileşenlerin kodunu gönderir.

Parçalı biçimde ilk yük süresi 64,4 milisaniyeden 44,3 milisaniyeye iner; kazanç
düğümlerden gelir. Gecikmeli biçimde süre 17,1 milisaniyeye düşer, çünkü ilk yükte yalnızca
70 düğüm ve 45 kilobayt vardır; 60 kilobayt sonraya bırakılır. Son sütun, durağan
bileşenlerin 16 kilobaytlık kodunun **hiç gönderilmediğini** gösterir: bu, ertelenen değil,
tümüyle ortadan kalkan bir maliyettir.

Gecikmeli biçimin bedeli, ertelenen bileşene ilk dokunuşta ödenir. Kullanıcı grafiğe
tıkladığında 48 kilobaytlık kod istenir ve etkileşim o kadar gecikir. Bu yüzden erteleme,
ilk ekranın dışındaki ya da kullanılma olasılığı düşük bileşenlere uygulanır; ilk
etkileşimin beklendiği yere değil.

#### İkisinin Birlikte Verdiği Sonuç

Akış ile parçalı canlandırma farklı iki kaleme dokunur ve birbirinin yerine geçmez. Akış,
belgenin ilk baytını öne çeker: kullanıcı içeriği daha erken görür. Parçalı canlandırma,
etkileşime hazır olma anını öne çeker: kullanıcı daha erken tıklayabilir.

İkisi birlikte kullanıldığında sayfanın her bölümü kendi hızında olgunlaşır. Kabuk hemen gelir,
bölümler sırayla belirir ve etkileşimli bölümler kodları geldikçe canlanır. Bu düzenin doğal
ölçüsü tek bir sayı değildir; bölüm başına "göründü" ve "canlandı" anlarıdır.

Ödenen bedel karmaşıklıktır. Bölüm sınırları, bölümlerin sırası, her bölümün yükleme ve
hata durumu, canlandırma tetikleyicileri — hepsi açıkça tanımlanmak zorundadır. Bir sayfada
üç bölüm ve iki ertelenmiş bileşen varsa sınanacak durum sayısı, tek parçalı bir yanıta
göre kat kat fazladır.

#### Özet

- Canlandırma, sunucudan gelen durağan işaretlemenin istemcideki bileşen ağacıyla
  eşleştirilip etkileşimli hâle getirilmesidir; kod indirme, ayrıştırma ve düğüm başına
  eşleşme olmak üzere üç maliyeti vardır.
- Eşleştirmenin koşulu belirlenimciliktir: sunucu ile istemcinin ürettiği ağaç ayrışırsa
  ilgili bölüm ya baştan üretilir ya da tutarsız kalır.
- Akışlı gönderim ilk baytı öne çeker ama toplam süreyi değiştirmez; gövde uzunluğu
  bilinmediği için `Content-Length` yerine parçalı aktarım kullanılır.
- Akışın bedeli, başlığın geri alınamamasıdır: durum kodu ve yönlendirme kararları ilk bayt
  gönderilmeden verilmeli, hatalar ise gövdenin içinde anlatılmalıdır.
- Parçalı canlandırma yalnızca etkileşimli bileşenleri, gecikmeli canlandırma yalnızca ilk
  ekrandakileri canlandırır; ölçülen sayfada ilk yük süresi 64,4 milisaniyeden sırasıyla
  44,3 ve 17,1 milisaniyeye iner.

#### Sonraki Adım

Bu iki teknik, sunucu ile kullanıcı arasındaki mesafeyi olduğu gibi bırakır. İlk dersteki
zincir hesabında ağ turu 60 milisaniye varsayılmıştı ve bu kalem kod değişikliğiyle
azalmıyordu: sinyal, aradaki fiziksel uzaklığı kat etmek zorundadır. Sunucu tek bir yerde
duruyorsa uzaktaki bir kullanıcı her turda bu bedeli öder. Sonraki ders, oluşturmanın
kullanıcıya coğrafi olarak yaklaştırılmasını ele alır: mesafenin gecikmeye çevrimi, kenarda
çalışan bir katmanın neyi yapıp neyi yapamayacağı ve kaynak sunucuyla iş bölümü.

### Kenar Oluşturma

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/kenar-olusturma

Oluşturmanın kullanıcıya coğrafi olarak yaklaştırılması; mesafenin gecikmeye çevrimi, kenar düğümünün aktarma, birleştirme ve tam üretim biçimleri, kenar çalışma zamanının kısıtları ve verinin nerede durduğu sorusu.

Önceki iki teknik yanıtın gönderim biçimini ve etkileşimin kazanılma anını değiştirdi;
sunucu ile kullanıcı arasındaki mesafeyi olduğu gibi bıraktı. İlk dersteki zincir hesabında
ağ turu 60 milisaniye varsayılmış ve bu kalemin kod değişikliğiyle azalmadığı
söylenmişti: sinyalin kat edeceği yol kısalmadıkça süre kısalmaz.

Bu ders yolu kısaltmayı ele alır: oluşturmanın bir bölümünün ya da tamamının, kullanıcıya
yakın duran bir düğüme taşınması.

#### Mesafe Bir Alt Sınır Koyar

Işık cam lifte boşluktakinden yavaş, yaklaşık 200.000 kilometre/saniye hızla ilerler.
Bu, hiçbir yazılımın aşamayacağı bir alt sınır verir.

```js
// mesafe.mjs — cografi uzakligin gecikmeye cevrimi ve kenarin kazandirdigi turlar
// Isik cam lifte yaklasik 200.000 km/s ile ilerler: bu bir alt sinirdir,
// gercek yollar duz cizgi degildir ve her atlamada isleme suresi eklenir.
const HIZ = 200000; // km/s
const tur = (km) => (2 * km / HIZ) * 1000; // ms, gidis-donus

console.log("mesafe (km)".padStart(12) + "tek yon (ms)".padStart(14) + "tur (ms)".padStart(11));
for (const km of [50, 500, 2500, 9000, 16000]) {
  console.log(String(km).padStart(12) + (tur(km) / 2).toFixed(1).padStart(14) +
    tur(km).toFixed(1).padStart(11));
}

// Bir sayfanin ilk gorunume kadar odedigi tur sayisi:
// 1 tasima el sikismasi + 1 sifreleme el sikismasi + 1 belge istegi + 1 alt kaynak turu.
const TUR_SAYISI = 4;
const SUNUCU_ISI = 40;   // ms, veri kaynagi + sablon
const KENAR_ISI = 2;     // ms, kenar dugumunun kendi isi

const KULLANICI_KENAR = 50;    // km
const KULLANICI_KAYNAK = 9000; // km

const yalnizKaynak = TUR_SAYISI * tur(KULLANICI_KAYNAK) + SUNUCU_ISI;
const kenarIsabet = TUR_SAYISI * tur(KULLANICI_KENAR) + KENAR_ISI;
const kenarIska = kenarIsabet + tur(KULLANICI_KAYNAK - KULLANICI_KENAR) + SUNUCU_ISI;

console.log("\nsenaryo".padEnd(34) + "sure (ms)".padStart(11) + "kaynaga gore".padStart(14));
for (const [ad, sure] of [
  ["yalniz kaynak sunucu", yalnizKaynak],
  ["kenar, kopyasi var", kenarIsabet],
  ["kenar, kaynaga sormasi gerekti", kenarIska],
]) {
  console.log(ad.padEnd(34) + sure.toFixed(1).padStart(11) +
    (sure / yalnizKaynak).toFixed(2).padStart(14));
}
```

```
 mesafe (km)  tek yon (ms)   tur (ms)
          50           0.3        0.5
         500           2.5        5.0
        2500          12.5       25.0
        9000          45.0       90.0
       16000          80.0      160.0

senaryo                            sure (ms)  kaynaga gore
yalniz kaynak sunucu                    400.0          1.00
kenar, kopyasi var                        4.0          0.01
kenar, kaynaga sormasi gerekti          133.5          0.33
```

Üst tablo tek başına bir tasarım kısıtıdır: dokuz bin kilometre uzaktaki bir sunucuya tek
bir gidiş-dönüş 90 milisaniyeden aşağı inemez. Bu sayı iyimserdir; gerçek yollar düz çizgi
değildir ve her ara düğümde işleme süresi eklenir.

Alt tablo turların **çarpan** etkisini gösterir. Sayfanın görünmesi dört tur istiyorsa uzak
sunucuda bu 360 milisaniyeye çıkar ve veri kaynağının 40 milisaniyelik işi toplamın onda birine
düşer. Sunucunun işini hızlandırmak burada boşa çabadır; azaltılması gereken şey turların
uzunluğudur.

Üçüncü satır kenarın sınırını da söyler: kenar düğümü isteği karşılayamayıp kaynağa
sormak zorunda kalırsa el sıkışmalar yakınlaşır ama içerik yine uzaktan gelir ve süre
kaynağın üçte birine iner, yüzde birine değil.

#### Kenar Düğümünün Üç İş Biçimi

Kenarda bir düğüm bulunması tek başına bir şey söylemez; belirleyici olan o düğümün ne
yaptığıdır. Üç biçim ayrı ayrı ölçülebilir. Aşağıdaki kaynak sunucuda coğrafi uzaklık,
yanıttan önce eklenen sabit bir gecikmeyle modellenmiştir; gerçek bir ağ ölçümü değildir.

```js
// kaynak-sunucu.mjs — uzaktaki kaynak sunucu
// Cografi uzaklik, yanittan once eklenen sabit bir gecikmeyle modellenmistir.
import { createServer } from "node:http";

const UZAKLIK = 80;  // ms, kaynaga gidip donmenin bedeli
const VERI_ISI = 40; // ms, veri kaynagindan okuma

const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

const KABUK_ICI = '<h1>Kuzey Yamac Olcum Istasyonu</h1>' +
  '<ul><li>06:00 -4.2 C</li><li>07:00 -3.8 C</li><li>08:00 -2.1 C</li></ul>';

const gonder = (yanit, govde) => {
  yanit.setHeader("Content-Type", "text/html; charset=utf-8");
  yanit.setHeader("Content-Length", Buffer.byteLength(govde));
  yanit.writeHead(200).end(govde);
};

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  await bekle(UZAKLIK);
  if (istek.url === "/sayfa") {
    await bekle(VERI_ISI);
    gonder(yanit, `<!doctype html><html lang="tr"><body>${KABUK_ICI}` +
      `<p>Sorumlu: (kaynakta uretildi)</p></body></html>\n`);
  } else if (istek.url === "/parca/kisisel") {
    await bekle(5); // kucuk, kisiye ozel parca
    gonder(yanit, "<p>Sorumlu: A. Yilmaz</p>");
  } else if (istek.url === "/veri/olcumler") {
    await bekle(VERI_ISI);
    yanit.setHeader("Content-Type", "application/json");
    const govde = JSON.stringify([
      { zaman: "06:00", sicaklik: -4.2 },
      { zaman: "07:00", sicaklik: -3.8 },
      { zaman: "08:00", sicaklik: -2.1 },
    ]);
    yanit.setHeader("Content-Length", Buffer.byteLength(govde));
    yanit.writeHead(200).end(govde);
  } else {
    yanit.writeHead(404).end();
  }
}).listen(8176, "127.0.0.1", () => console.log("kaynak: 127.0.0.1:8176"));
```

```js
// kenar-sunucu.mjs — kullaniciya yakin, kod calistirabilen kenar dugumu
// Kaynak sunucuya erisim 8176 uzerinden; kullaniciya olan uzaklik ihmal edilir.
import { createServer } from "node:http";

const KAYNAK = "http://127.0.0.1:8176";

// Kenarin elindeki kopyalar: kabuk derleme ciktisindan, olcumler kaynaktan gelir.
const KABUK = (icerik, kisisel) =>
  `<!doctype html><html lang="tr"><body>${icerik}${kisisel}</body></html>\n`;
const BASLIK = "<h1>Kuzey Yamac Olcum Istasyonu</h1>";
let olcumKopyasi = null;

const gonder = (yanit, govde) => {
  yanit.setHeader("Content-Type", "text/html; charset=utf-8");
  yanit.setHeader("Content-Length", Buffer.byteLength(govde));
  yanit.writeHead(200).end(govde);
};

const liste = (olcumler) =>
  "<ul>" + olcumler.map((o) => `<li>${o.zaman} ${o.sicaklik} C</li>`).join("") + "</ul>";

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  if (istek.url === "/gecis") {
    // Kenar yalnizca aktarir: butun is kaynakta yapilir.
    const kaynakYaniti = await fetch(`${KAYNAK}/sayfa`);
    gonder(yanit, await kaynakYaniti.text());
  } else if (istek.url === "/birlestir") {
    // Kabuk kenarda hazir; yalnizca kisiye ozel kucuk parca kaynaktan istenir.
    const parca = await (await fetch(`${KAYNAK}/parca/kisisel`)).text();
    gonder(yanit, KABUK(BASLIK + liste(olcumKopyasi ?? []), parca));
  } else if (istek.url === "/kenarda") {
    // Sayfanin tamami kenardaki kopyadan uretilir; kaynak hic sorulmaz.
    gonder(yanit, KABUK(BASLIK + liste(olcumKopyasi ?? []), "<p>Sorumlu: (genel)</p>"));
  } else if (istek.url === "/tazele") {
    // Kenarin veri kopyasini kaynaktan yeniler.
    olcumKopyasi = await (await fetch(`${KAYNAK}/veri/olcumler`)).json();
    gonder(yanit, `<p>${olcumKopyasi.length} olcum alindi</p>\n`);
  } else {
    yanit.writeHead(404).end();
  }
}).listen(8177, "127.0.0.1", () => console.log("kenar: 127.0.0.1:8177"));
```

Aşağıdaki betik bu iki dosyayla aynı dizinde çalıştırılır. Bağlantı noktaları 8176 ve 8177
keyfîdir ve boş olmalıdır; kullanımdaysa üç dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# Iki sunucuyu baslatir, uc is bolumunu olcer, sonra durdurur.
node kaynak-sunucu.mjs > /dev/null &
kaynak=$!
node kenar-sunucu.mjs > /dev/null &
kenar=$!
sleep 1

BICIM='ilk bayt %{time_starttransfer} s   govde %{size_download} B\n'

printf '%-38s' "0) kenarin kopyasini tazelemesi"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8177/tazele

printf '%-38s' "1) dogrudan kaynak sunucu"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8176/sayfa

printf '%-38s' "2) kenar yalnizca aktariyor"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8177/gecis

printf '%-38s' "3) kenar birlestiriyor"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8177/birlestir

printf '%-38s' "4) kenar tumuyle kendisi uretiyor"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8177/kenarda

echo "--- kenarda uretilen govde ---"
curl -sS http://127.0.0.1:8177/kenarda

kill "$kaynak" "$kenar"
```

```
0) kenarin kopyasini tazelemesi       ilk bayt 0.154206 s   govde 22 B
1) dogrudan kaynak sunucu             ilk bayt 0.124900 s   govde 195 B
2) kenar yalnizca aktariyor           ilk bayt 0.129912 s   govde 195 B
3) kenar birlestiriyor                ilk bayt 0.094019 s   govde 185 B
4) kenar tumuyle kendisi uretiyor     ilk bayt 0.000815 s   govde 183 B
```

Sıfırıncı satır ölçüme girmez; kenarın veri kopyasını doldurduğu hazırlık adımıdır. Süre
alanları makineye bağlıdır ve yorumu oranlar taşır.

**İkinci satır kenarın yanlış kullanımıdır.** Yalnızca aktaran bir düğüm, kaynağın süresine kendi
işini ekler ve doğrudan erişimden **yavaştır** — ölçülen çalıştırmada yaklaşık yüzde dört. Farkın
büyüklüğü çalıştırmaya göre değişir ama işareti değişmez: araya bir düğüm koymanın tek başına bir
kazancı yoktur; kazanç, o düğümün üstlendiği işten gelir.

**Üçüncü satır iş bölümüdür.** Kabuk ve ölçüm listesi kenarda hazırdır; kaynaktan yalnızca
kişiye özel küçük parça istenir. Süre, doğrudan erişimin yaklaşık dörtte üçüne iner. Kazanç
kaynağa hiç gitmemekten değil, kaynaktan **az şey** istemekten gelir.

**Dördüncü satır tam kenarda üretimdir.** Kaynak hiç sorulmaz; süre iki mertebe kısalır —
ölçülen çalıştırmada yaklaşık yüz elli kat. Bunun koşulu, sayfayı üretmek için gereken her
şeyin kenarda bulunmasıdır.

#### Kenar Çalışma Zamanının Kısıtları

Kenarda kod çalıştırmak, kaynak sunucuda kod çalıştırmakla aynı şey değildir. Kısıtlar
kenarın tanımından doğar: çok sayıda düğümde, her düğümde az kaynakla, çok sayıda kiracı
yan yana çalışır.

**Süreç uzun yaşamaz.** İstek dışında ayakta duran bir bellek durumu varsayılamaz; bir
düğümde tutulan sayaç başka bir düğümde yoktur. Durum, ya isteğin kendisinde ya paylaşımlı
bir depoda tutulur.

**İşlem ve bellek bütçesi dardır.** Ağır bir şablon işleme, büyük bir bağımlılık ağacı ya
da uzun süren bir hesap kenara uygun değildir. Kenarda çalışacak kod, kaynak sunucuda
çalışan koddan küçüktür ve bu bir tercih değil bir sınırdır.

**İlk çağrının maliyeti ayrıdır.** Uzun süre çağrılmamış bir kod parçası yeniden
yüklenirken bir başlatma süresi doğar. Kenarın kazandırdığı milisaniyeler bu süreyle aynı
mertebede olabilir; bu yüzden nadiren çağrılan yollar kenara taşınarak hızlanmaz.

**Ortam düğümden düğüme aynı değildir.** Kenarda çalışan kod, kaynak sunucudakinin tüm
yeteneklerine sahip olmayabilir. Dosya sistemi, uzun ömürlü bağlantılar ve kimi
kitaplıklar bulunmayabilir; kod bu varsayımla yazılır.

#### Asıl Soru Verinin Nerede Olduğudur

Ölçümün dördüncü satırındaki kazanç, kenarın ölçüm listesinin bir kopyasını taşımasından
doğdu. Bu kopya sıfırıncı adımda kaynaktan alınmıştı ve alındığı andan itibaren eskimeye
başlar.

Buradan kenar oluşturmanın gerçek kuralı çıkar: **oluşturma, verisinin yanına taşınabildiği
kadar taşınabilir.** Kenara taşınan kod, kaynaktaki veriyi her istekte soruyorsa mesafe
yeniden ödenir; üçüncü satır bu durumun ölçüsüdür. Kazancın tamamı ancak veri de kenarda
olduğunda alınır.

Verinin kenara taşınması üç biçimde olur ve üçü de bir bedel taşır. Nadiren değişen veri
kopyalanır; bedeli tazeliktir ve artımlı yeniden üretim dersindeki pencere hesabı burada da
geçerlidir. Kullanıcıya bağlı küçük veri isteğin kendisinde taşınır; bedeli, bu verinin
kurcalanamayacak biçimde imzalanması gereğidir. Geri kalan her şey kaynakta kalır; bedeli,
o bölümler için mesafenin ödenmesidir.

Bu üçlü ayrım, sayfanın bölümlere ayrılmasını gerektirir. İstasyon sayfasında kabuk ve ölçüm
listesi kopyalanabilir, sorumlu adı istekte taşınabilir ve düzenleme yetkileri kaynakta sınanır.
Sayfanın tek parça olduğu bir tasarımda bu ayrım yapılamaz ve sayfanın tamamı en kısıtlayıcı
bölümün kuralına uyar.

#### Özet

- Coğrafi mesafe gecikmeye bir alt sınır koyar: dokuz bin kilometre için tek gidiş-dönüş
  90 milisaniyeden aşağı inmez ve sayfa başına ödenen tur sayısıyla çarpılır.
- Kenarda bir düğüm bulunması tek başına kazanç değildir; yalnızca aktaran bir düğüm
  doğrudan erişimden yavaştır.
- Kaynaktan yalnızca küçük bir parça isteyen kenar, süreyi doğrudan erişimin dörtte üçüne;
  hiç sormayan kenar ise yüzde birinin altına indirir.
- Kenar çalışma zamanı uzun ömürlü bellek durumu varsaymaz, dar bir işlem ve bellek
  bütçesiyle çalışır, ilk çağrıda bir başlatma süresi doğurur ve yetenekleri kaynak
  sunucununkinden dardır.
- Oluşturma ancak verisinin taşınabildiği kadar kenara taşınabilir; sayfanın bölümlere
  ayrılmadığı bir tasarımda tamamı en kısıtlayıcı bölümün kuralına uyar.

#### Sonraki Adım

Altı model ayrı ayrı ölçüldü ve her biri belirli bir durumda üstün çıktı. Geriye bu
ölçümleri tek bir karara bağlamak kalıyor. Kuzey Yamaç sitesinde yedi ayrı sayfa türü
vardır ve hepsinin aynı modelle üretilmesi için hiçbir neden yoktur: tanıtım sayfası
değişmez, ölçüm listesi dakikada bir değişir, arşiv sayfaları binlercedir, panel kişiye
özeldir. Sonraki ders bu envanteri üç eksene — içerik tazeliği, kişiselleştirme ve ölçek —
oturtur ve her sayfa için modeli bu eksenlerden türeten bir karar kuralı kurar.

### Strateji Seçimi

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/strateji-secimi

Oluşturma modelinin sayfa başına seçilmesi; içerik tazeliği, kişiselleştirme ve ölçek eksenlerinin ölçüye bağlanması, sıralı bir karar kuralı ve tek tip politikalarla karışık politikanın günlük iş ve ihlal sayısı üzerinden karşılaştırılması.

Altı model ayrı ayrı ölçüldü ve her biri belirli bir durumda üstün çıktı. İstemcide
oluşturma sonraki gezinmeleri ağdan kurtardı, sunucuda oluşturma ilk içeriği öne çekti,
statik üretim istek anındaki işi sıfırladı, artımlı yeniden üretim tazeliği derleme
sıklığından kurtardı, akış ile canlandırma bölme aralıkları daralttı, kenar mesafeyi kesti.

Geriye tek bir soru kalıyor: belirli bir sayfa için hangisi? Bu ders o soruyu bir zevk
meselesi olmaktan çıkarıp üç ölçülebilir eksene bağlar.

#### Üç Eksen

**İçerik tazeliği**, sayfanın gösterdiği bilginin en fazla ne kadar eski olabileceğidir ve saniye
cinsinden yazılır. Bu bir tercih değil, içeriğin doğasından gelen bir gerekliliktir: istasyonun
kuruluş bilgisi hiç eskimez, günlük arşiv kapandıktan sonra değişmez ve canlı ölçüm listesi bir
dakikadan eski olduğunda yanıltıcı hâle gelir.

Tazeliği yazarken iki şey ayrılır: içeriğin **değişme sıklığı** ve gösterilen değerin
**kabul edilebilir yaşı**. Ölçüm dakikada bir değişebilir ama sayfada ölçüm zamanı yazılıyorsa
beş dakikalık bir yaş kabul edilebilir olabilir. Karar, ikincisine göre verilir.

**Kişiselleştirme**, yanıtın kullanıcıya göre değişip değişmediğidir ve üç değer alır. Yanıt
herkese aynıysa kişiselleştirme yoktur. Sayfanın yalnızca küçük bir bölümü kişiye
bağlıysa — bir ad, bir yetki rozeti, bir eşik uyarısı — kişiselleştirme bölümseldir. Sayfanın
yapısı kullanıcıya göre kuruluyorsa tamdır.

Bu üçlü ayrım önemlidir çünkü bölümsel kişiselleştirme, sayfanın geri kalanının
önbelleklenebilirliğini korur. Kenar Oluşturma dersindeki üçüncü ölçüm bunun karşılığıydı:
kabuk kenarda hazırken yalnızca küçük parçanın kaynaktan istenmesi süreyi dörtte üçe
indirmişti.

**Ölçek** iki ayrı sayıdır ve karıştırılmamalıdır. **Sayfa sayısı** derleme maliyetini
belirler; **istek hacmi** çalışma zamanı maliyetini belirler. Statik Site Üretimi dersinde
ölçülen doğrusallık birincisiyle, Sunucuda Oluşturma dersindeki istek başına maliyet
ikincisiyle ilgilidir. Dört bin arşiv sayfası ve günde sekiz yüz panel isteği, tümüyle
farklı iki sınır üretir.

#### Envanter ve Karar Kuralı

Eksenler yazıldıktan sonra karar bir tablo aramasına döner. Aşağıdaki kural sıralı sınanır;
ilk tutan kural kazanır ve sıra rastgele değildir: kişiselleştirme, önbelleklenebilirliği
tümüyle kaldırdığı için önce sınanır; ölçek, derleme yolunu kapattığı için ondan sonra
gelir; tazelik en sonda pencereyi belirler.

```js
// karar.mjs — istasyon sitesinin sayfa envanteri, model secimi ve politika karsilastirmasi
// Girdiler varsayimdir; cikti bu varsayimlarin aritmetigidir, olcum degildir.

const GUN = 86400;               // saniye
const URETIM_MS = 40;            // bir sayfayi uretmenin sunucu isi
const PARCA_MS = 5;              // kenarda birlestirmede kaynaktan istenen kucuk parca
const SAYFA_ESIGI = 2000;        // bunun ustunde tumunu her derlemede uretmek pahali

// tazelik: icerigin en fazla ne kadar eski sunulabilecegi (saniye), Infinity = degismez
// kisisel: "yok" | "bolumsel" | "tam"
const ENVANTER = [
  { yol: "/",              tazelik: GUN,      kisisel: "yok",      sayfa: 1,    istek: 4000 },
  { yol: "/olcumler",      tazelik: 60,       kisisel: "yok",      sayfa: 1,    istek: 30000 },
  { yol: "/olcumler/:gun", tazelik: Infinity, kisisel: "yok",      sayfa: 4000, istek: 12000 },
  { yol: "/panel",         tazelik: 5,        kisisel: "tam",      sayfa: 1,    istek: 800 },
  { yol: "/harita",        tazelik: 7 * GUN,  kisisel: "yok",      sayfa: 1,    istek: 2500 },
  { yol: "/hakkinda",      tazelik: Infinity, kisisel: "yok",      sayfa: 3,    istek: 900 },
  { yol: "/uyari",         tazelik: 60,       kisisel: "bolumsel", sayfa: 1,    istek: 5000 },
];

// Karar kurali: sirali sinanir, ilk tutan kural kazanir.
function model(s) {
  if (s.kisisel === "tam") return "sunucuda olusturma";
  if (s.kisisel === "bolumsel") return "kenarda birlestirme";
  if (s.sayfa > SAYFA_ESIGI) return "artimli yeniden uretim";
  if (s.tazelik === Infinity) return "statik site uretimi";
  if (s.tazelik >= 3600) return "statik site uretimi";
  if (s.tazelik >= 30) return "artimli yeniden uretim";
  return "sunucuda olusturma";
}

// Gunluk sunucu isi (saniye). Artimli uretimde ilk iska bir kez odendigi icin
// kararli duruma bakilir; degismez icerik yeniden uretilmez.
function gunlukIs(s, secim) {
  switch (secim) {
    case "statik site uretimi":
      return (s.sayfa * URETIM_MS) / 1000;                       // gunde bir derleme
    case "sunucuda olusturma":
      return (s.istek * URETIM_MS) / 1000;
    case "kenarda birlestirme":
      return (s.istek * PARCA_MS) / 1000;
    case "artimli yeniden uretim": {
      if (s.tazelik === Infinity) return 0;
      const uretim = Math.min(s.istek, s.sayfa * (GUN / s.tazelik));
      return (uretim * URETIM_MS) / 1000;
    }
    default:
      return 0;
  }
}

console.log("yol".padEnd(16) + "tazelik".padStart(10) + "kisisel".padStart(11) +
  "sayfa".padStart(7) + "istek".padStart(8) + "   secilen model");
for (const s of ENVANTER) {
  const t = s.tazelik === Infinity ? "degismez" : `${s.tazelik} s`;
  console.log(s.yol.padEnd(16) + t.padStart(10) + s.kisisel.padStart(11) +
    String(s.sayfa).padStart(7) + String(s.istek).padStart(8) + "   " + model(s));
}

// Politikalar: uc tek tip secim ve karar kuralinin urettigi karisik secim.
const POLITIKALAR = [
  ["hepsi sunucuda", () => "sunucuda olusturma"],
  ["hepsi statik", () => "statik site uretimi"],
  ["hepsi artimli", () => "artimli yeniden uretim"],
  ["karar kurali", model],
];

console.log("\npolitika".padEnd(18) + "gunluk is (s)".padStart(15) +
  "tazelik ihlali".padStart(16) + "kisisellestirme ihlali".padStart(24));
for (const [ad, sec] of POLITIKALAR) {
  let is = 0, tazelikIhlali = 0, kisiselIhlali = 0;
  for (const s of ENVANTER) {
    const secim = sec(s);
    is += gunlukIs(s, secim);
    // Statik uretim gunde bir derlenir: bir gunden kisa tazelik istegini karsilamaz.
    if (secim === "statik site uretimi" && s.tazelik < GUN) tazelikIhlali += 1;
    // Kisiye ozel bolum, herkese ayni yaniti veren modellerde uretilemez.
    if (s.kisisel !== "yok" &&
        (secim === "statik site uretimi" || secim === "artimli yeniden uretim")) {
      kisiselIhlali += 1;
    }
  }
  console.log(ad.padEnd(18) + is.toFixed(1).padStart(15) +
    String(tazelikIhlali).padStart(16) + String(kisiselIhlali).padStart(24));
}
```

```
yol                tazelik    kisisel  sayfa   istek   secilen model
/                  86400 s        yok      1    4000   statik site uretimi
/olcumler             60 s        yok      1   30000   artimli yeniden uretim
/olcumler/:gun    degismez        yok   4000   12000   artimli yeniden uretim
/panel                 5 s        tam      1     800   sunucuda olusturma
/harita           604800 s        yok      1    2500   statik site uretimi
/hakkinda         degismez        yok      3     900   statik site uretimi
/uyari                60 s   bolumsel      1    5000   kenarda birlestirme

politika           gunluk is (s)  tazelik ihlali  kisisellestirme ihlali
hepsi sunucuda             2208.0               0                       0
hepsi statik                160.3               3                       2
hepsi artimli               147.2               0                       2
karar kurali                114.8               0                       0
```

Üst tablonun üçüncü satırı kuralın sırasını haklı çıkarır: arşiv sayfaları hiç değişmez ve
saf mantıkla statik üretime aittir, ama dört bin sayfa eşiğin üstündedir. Kural bu yüzden
onları artımlı yeniden üretime yollar — sayfalar ilk istendiklerinde üretilir, sonra
değişmedikleri için bir daha üretilmezler. Günlük iş hesabında bu satırın katkısı sıfırdır.

Yedinci satır bölümsel kişiselleştirmenin karşılığıdır. Uyarı sayfasının gövdesi herkese aynıdır;
yalnızca eşik uyarısı kişiye bağlıdır. Sayfanın tamamı sunucuda üretilseydi istek başına 40
milisaniye ödenecekti; kenarda birleştirmeyle 5 milisaniyeye iner.

#### Politikaların Karşılaştırılması

Alt tablo, tek tip bir politikanın neden yetmediğini üç sütunda gösterir.

**Hepsi sunucuda** hiçbir kısıtı ihlal etmez ve en pahalıdır: günde 2208 saniye sunucu işi,
karar kuralının yaklaşık on dokuz katı. Bütün sayfaları en kısıtlayıcı sayfanın kuralına
tabi tutmanın bedeli budur.

**Hepsi statik** ucuza gelir ama üç sayfa türünün tazelik gereksinimini ve iki sayfa türünün
kişiselleştirmesini karşılayamaz. Bu sütunlar para birimiyle ölçülmez; karşılanmayan bir
gereksinim, ucuzluğun telafi edemeyeceği bir kusurdur.

**Hepsi artımlı** tazelik ihlalini kapatır — pencere içeriğin gereksinimine ayarlanabilir —
ama kişiselleştirmeyi çözemez, çünkü paylaşılan bir kopya kişiye özel olamaz.

**Karar kuralı** hem ihlalsizdir hem de en ucuzdur. Bu bir rastlantı değildir: her sayfa
kendi kısıtlarının izin verdiği en ucuz modeli aldığı için toplam da en düşük çıkar.
Karışık politikanın maliyeti, tek tip politikalarınkinin ortalaması değil, alt sınırıdır.

#### Eksenlerin Yakalamadığı Ölçütler

Üç eksen kararın çoğunu verir; hepsini vermez. Dört ölçüt daha vardır ve bunlar seçimi
değiştirebilir.

**İşaretlemenin makine tarafından okunması.** İçeriğin betik çalıştırmayan istemcilerce
okunması gerekiyorsa istemcide oluşturma elenir. İlk derste ölçülen sıfır, bu kısıtın
somut hâliydi.

**İlk ziyaret oranı.** Ziyaretçilerin çoğu tek sayfa görüp ayrılıyorsa istemcide
oluşturmanın sonraki gezinmelerde kazandırdığı hız hiç kullanılmaz; ilk yükleme bedeli
sürekli ödenir. Aynı arayüz, günde saatlerce açık kalan bir panel olduğunda tersine döner.

**Hata dayanıklılığı.** Veri kaynağı yanıt vermediğinde ne olacağı modele göre değişir:
statik çıktı sunulmaya devam eder, artımlı üretim eski kopyayı verir, sunucuda oluşturma
belgeyi hiç üretemez. Kesinti sırasında görünmesi gereken sayfalar bu ölçüte göre seçilir.

**İşletme karmaşıklığı.** Beş ayrı modelin bir arada bulunduğu bir sitede, her modelin ayrı
bir dağıtım yolu, ayrı bir önbellek davranışı ve ayrı bir hata biçimi vardır. Karar
kuralının kazandırdığı sunucu saatleri, sınama ve tanı yükündeki artışla birlikte
değerlendirilir. Az sayıda modelle çalışmak da bir seçimdir.

#### Karar Yeniden Gözden Geçirilir

Envanterdeki değerler sabit değildir. Arşiv sayfa sayısı her gün bir artar ve eşiği
geçtiğinde seçim değişir. Bir sayfaya kişiye özel bir bölüm eklendiğinde kişiselleştirme
sütunu "yok"tan "bölümsel"e döner ve model değişir. İstek hacmi on kat arttığında sunucuda
oluşturmanın maliyeti aynı oranda büyür.

Bu yüzden karar kuralı koda gömülü bir tablo olarak tutulur ve envanterin değerleri
ölçümden beslenir: sayfa sayısı derleme çıktısından, istek hacmi erişim kayıtlarından,
tazelik gereksinimi içeriğin sahibinden. Tahminle doldurulmuş bir envanter, yanlış bir
kuralın doğru sonucunu verir.

#### Özet

- Oluşturma modeli site geneline değil sayfa türüne göre seçilir; kararın üç ölçülebilir
  ekseni içerik tazeliği, kişiselleştirme derecesi ve ölçektir.
- Ölçek iki ayrı sayıdır: sayfa sayısı derleme maliyetini, istek hacmi çalışma zamanı
  maliyetini belirler.
- Sıralı bir karar kuralı — önce kişiselleştirme, sonra ölçek, en sonda tazelik — envanteri
  modellere eşler ve seçimi yeniden üretilebilir kılar.
- Karışık politika, ölçülen envanterde hem kısıt ihlalsizdir hem de tek tip politikaların
  en ucuzundan daha ucuzdur; her sayfa kendi kısıtlarının izin verdiği en ucuz modeli alır.
- Üç eksen dışında dört ölçüt daha karara girer: işaretlemenin makine tarafından okunması,
  ilk ziyaret oranı, veri kaynağı kesildiğinde beklenen davranış ve işletme karmaşıklığı.

#### Sonraki Adım

Strateji seçildi: her sayfa türü hangi modelle üretileceğini biliyor. Ama modellerin hepsi
aynı şeyi varsayıyor — çalıştırılabilir bir çıktının hazır olduğunu. Statik üretim bir
derleyicinin çalışmasını, sunucuda oluşturma sunucuya yüklenecek bir paketi, kenarda
birleştirme kenara gönderilecek küçük bir modülü, canlandırma ise istemciye inecek bileşen
kodunu gerektirir. Bu çıktıları üreten şey, kaynak dosyalar arasındaki bağımlılık ilişkisini
çözüp hangi kodun hangi parçaya gireceğine karar veren bir süreçtir. Sonraki konu bununla
açılır: Modül Paketleme dersi, bağımlılık çizgesinin nasıl kurulduğunu ve bu çizgeden kaç
parçanın hangi ölçütle üretildiğini ele alır.

## Derleme ve Paketleme

Kaynak ağacın çalıştırılabilir çıktıya dönüşmesi; bağımlılık çizgesinden parça üretimi, kod bölme, ağaç sarsma, varlık boru hattı, içerik karmalı adlandırma, geliştirme sunucusu ve derleme zamanı yapılandırması.

### Modül Paketleme

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/modul-paketleme

Kaynak ağacın çalıştırılabilir çıktıya dönüşmesi; bağımlılık çizgesinin giriş noktalarından çıkarılması, değerlendirme sırası, kullanım imzasına göre çıktı parçası oluşturma ve parçanın içindeki çalışma zamanı.

Strateji seçildi. Kuzey Yamaç Ölçüm İstasyonu sitesinin hangi sayfasının nerede
üretileceği belli: tanıtım sayfası derleme zamanında, ölçüm listesi sunucuda, sorumlu
paneli istemcide, yönlendirme kararı kenarda. Bu kararların ortak bir varsayımı vardı ve
o varsayım henüz karşılanmadı. Her strateji, çalıştırılabilir bir **çıktı** ister:
derleme zamanı üretim, sayfaları basacak bir programın hazır olmasını; sunucuda oluşturma,
sunucuya yüklenecek bir modülü; istemcide oluşturma, tarayıcıya inecek bir betiği.

Kaynak ağaç bunların hiçbiri değildir. Kaynakta onlarca küçük dosya, aralarında içe
aktarım ilişkileri ve hedef ortamın çözemeyeceği belirteçler vardır. Bu dersin sorusu
şudur: kaynak dosyalar arasındaki bağımlılık ilişkisinden hangi çıktı parçaları, hangi
ölçütle üretilir?

#### Kaynak Düzeni ile Çıktı Düzeni Aynı Şey Değildir

Modüller, Araçlar ve Ekosistem kursundaki paketleyici dersi bu ayrımı kurmuştu: kaynak
düzeni okunabilirlik için, çıktı düzeni hedef ortamın kısıtları için tasarlanır. Orada
tek girişli tek çıktı üretilmişti. Oluşturma stratejisi birden çok hedef seçtiği anda
soru değişir. Aynı kaynak ağaçtan hem tarayıcıya inecek bir betik hem de sunucuda
çalışacak bir modül üretiliyorsa, iki çıktının ortak modülleri vardır. Bu ortaklığın nasıl
ele alındığı, hem çıktı boyutunu hem de önbellekleme davranışını belirler.

**Modül paketleme (module bundling)**, giriş noktalarından erişilen modül kümesini bulup
bu kümeyi hedefe uygun sayıda dosyaya bölme işidir. İki ayrı karar içerir: çizgede kimin
kime bağlı olduğunun çıkarılması ve bu çizgenin parçalara ayrılması.

#### Uygulamanın Kaynak Ağacı

Kurs boyunca aynı kaynak ağaç kullanılacak. Aşağıdaki blok onu üretir.

```bash
mkdir -p kaynak

cat > kaynak/birim.js <<'EOF'
export const SICAKLIK_ALT = -60;
export const SICAKLIK_UST = 60;
export const sinirdaMi = (c) => c >= SICAKLIK_ALT && c <= SICAKLIK_UST;
export const fahrenhayt = (c) => c * 9 / 5 + 32;
EOF

cat > kaynak/tarih.js <<'EOF'
const iki = (s) => String(s).padStart(2, "0");
export const saatDakika = (d) => iki(new Date(d).getUTCHours()) + ":" + iki(new Date(d).getUTCMinutes());
export const gunAnahtari = (d) => new Date(d).toISOString().slice(0, 10);
EOF

cat > kaynak/bicim.js <<'EOF'
import { saatDakika } from "./tarih.js";
import { sinirdaMi, fahrenhayt } from "./birim.js";

export function olcumSatiri(o) {
  const uyari = sinirdaMi(o.sicaklik) ? "" : " (sinir disi)";
  return saatDakika(o.damga) + "  " + o.sicaklik.toFixed(1) + " C  %" + o.nem + uyari;
}
export const ikiliBirim = (o) => o.sicaklik.toFixed(1) + " C / " + fahrenhayt(o.sicaklik).toFixed(1) + " F";
EOF

cat > kaynak/sablon.js <<'EOF'
import { olcumSatiri } from "./bicim.js";

export const liste = (olcumler) =>
  "<ul>" + olcumler.map((o) => "<li>" + olcumSatiri(o) + "</li>").join("") + "</ul>";
export const baslik = (istasyon) => "<h1>" + istasyon + "</h1>";
EOF

cat > kaynak/grafik.js <<'EOF'
import { SICAKLIK_ALT, SICAKLIK_UST } from "./birim.js";

const olcekle = (v, uzunluk) => ((v - SICAKLIK_ALT) / (SICAKLIK_UST - SICAKLIK_ALT)) * uzunluk;

export function sicaklikYolu(olcumler, genislik, yukseklik) {
  const adim = olcumler.length > 1 ? genislik / (olcumler.length - 1) : 0;
  return olcumler
    .map((o, i) => (i ? "L" : "M") + (i * adim).toFixed(1) + " " +
      (yukseklik - olcekle(o.sicaklik, yukseklik)).toFixed(1))
    .join(" ");
}
EOF

cat > kaynak/arsiv.js <<'EOF'
import { gunAnahtari } from "./tarih.js";
import { liste } from "./sablon.js";

export function gunlereBol(olcumler) {
  const kova = new Map();
  for (const o of olcumler) {
    const g = gunAnahtari(o.damga);
    kova.set(g, [...(kova.get(g) ?? []), o]);
  }
  return kova;
}
export const arsivSayfasi = (gun, olcumler) => "<h2>" + gun + "</h2>" + liste(olcumler);
EOF

cat > kaynak/giris-tarayici.js <<'EOF'
import { liste, baslik } from "./sablon.js";
import { sicaklikYolu } from "./grafik.js";

export function baslat(kok, istasyon, olcumler) {
  kok.innerHTML = baslik(istasyon) + liste(olcumler);
  return sicaklikYolu(olcumler, 320, 120);
}
EOF

cat > kaynak/giris-sunucu.js <<'EOF'
import { liste, baslik } from "./sablon.js";
import { gunlereBol, arsivSayfasi } from "./arsiv.js";

export function sayfaUret(istasyon, olcumler) {
  const gunluk = [...gunlereBol(olcumler)].map(([g, l]) => arsivSayfasi(g, l));
  return { canli: baslik(istasyon) + liste(olcumler), arsiv: gunluk };
}
EOF
```

Sekiz modül, iki giriş noktası. Tarayıcı girişi ekranı çizer ve sıcaklık grafiğini üretir;
sunucu girişi aynı listeyi ve günlük arşiv sayfalarını üretir. Biçimlendirme, şablon,
birim ve tarih modülleri ikisinde de kullanılır.

#### Çizgenin Çıkarılması

Çizge, giriş noktalarından başlayan bir dolaşımla bulunur. Her modül bir düğüm, her içe
aktarım bir yönlü kenardır. Veri Yapıları kursundaki enine arama, burada dosya sistemi
üzerinde çalışır: kuyruktan bir yol alınır, kaynağı okunur, belirteçleri çözülür, bulunan
komşular kuyruğa eklenir.

```js
// cizge.mjs — kaynak agacindan bagimlilik cizgesi ve cikti parcalari.
import { readFileSync, statSync } from "node:fs";
import { dirname, join, relative } from "node:path";

const GIRISLER = ["kaynak/giris-tarayici.js", "kaynak/giris-sunucu.js"];
const ICE_AKTARIM = /^\s*(?:import|export)[^"']*from\s*["']([^"']+)["']/gm;

function cizgeCikar(girisler) {
  const dugum = new Map();                      // yol -> { boyut, komsu[] }
  const kuyruk = [...girisler];
  while (kuyruk.length > 0) {
    const yol = kuyruk.shift();
    if (dugum.has(yol)) continue;
    const kaynak = readFileSync(yol, "utf8");
    const komsu = [...kaynak.matchAll(ICE_AKTARIM)]
      .map(([, belirtec]) => relative(".", join(dirname(yol), belirtec)));
    dugum.set(yol, { boyut: statSync(yol).size, komsu });
    kuyruk.push(...komsu);
  }
  return dugum;
}

function kapanis(cizge, kok) {                  // kokten erisilen tum dugumler
  const gorulen = new Set(), yigit = [kok];
  while (yigit.length > 0) {
    const yol = yigit.pop();
    if (gorulen.has(yol)) continue;
    gorulen.add(yol);
    yigit.push(...cizge.get(yol).komsu);
  }
  return gorulen;
}

function degerlendirmeSirasi(cizge, kok) {      // sonradan-gezinme: bagimlilik once
  const sira = [], gorulen = new Set();
  (function gez(yol) {
    if (gorulen.has(yol)) return;
    gorulen.add(yol);
    for (const k of cizge.get(yol).komsu) gez(k);
    sira.push(yol);
  })(kok);
  return sira;
}

const cizge = cizgeCikar(GIRISLER);
const ad = (y) => y.replace("kaynak/", "");
const kenarSayisi = [...cizge.values()].reduce((t, d) => t + d.komsu.length, 0);

console.log("-- cizge --");
console.log("dugum:", cizge.size, " kenar:", kenarSayisi);
for (const [yol, d] of [...cizge].sort()) {
  console.log("  " + ad(yol).padEnd(20) + String(d.boyut).padStart(5) + " B  -> " +
    (d.komsu.map(ad).join(", ") || "(yaprak)"));
}

console.log("-- degerlendirme sirasi (tarayici girisi) --");
console.log("  " + degerlendirmeSirasi(cizge, GIRISLER[0]).map(ad).join(" -> "));

const kapanislar = new Map(GIRISLER.map((g) => [g, kapanis(cizge, g)]));
console.log("-- giris kapanislari --");
for (const [g, k] of kapanislar) {
  const bayt = [...k].reduce((t, y) => t + cizge.get(y).boyut, 0);
  console.log("  " + ad(g).padEnd(20) + String(k.size).padStart(2) + " modul  " +
    String(bayt).padStart(5) + " B");
}

// Imza: modulu isteyen girislerin kumesi. Ayni imza = ayni cikti parcasi.
const imza = new Map();
for (const yol of cizge.keys()) {
  const isteyen = GIRISLER.filter((g) => kapanislar.get(g).has(yol));
  const anahtar = isteyen.map(ad).join(" + ");
  if (!imza.has(anahtar)) imza.set(anahtar, []);
  imza.get(anahtar).push(yol);
}

console.log("-- cikti parcalari --");
let toplamParca = 0;
for (const [anahtar, moduller] of [...imza].sort((a, b) => b[0].length - a[0].length)) {
  const bayt = moduller.reduce((t, y) => t + cizge.get(y).boyut, 0);
  toplamParca += bayt;
  console.log("  isteyen: " + anahtar);
  console.log("    " + String(bayt).padStart(5) + " B  " + moduller.map(ad).sort().join(", "));
}
const kaynakToplam = [...cizge.values()].reduce((t, d) => t + d.boyut, 0);
console.log("-- toplam --");
console.log("  kaynak:", kaynakToplam, "B   parcalar:", toplamParca, "B");
console.log("  kopyalanan modul yok:", kaynakToplam === toplamParca);
```

```
$ node cizge.mjs
-- cizge --
dugum: 8  kenar: 10
  arsiv.js              367 B  -> tarih.js, sablon.js
  bicim.js              387 B  -> tarih.js, birim.js
  birim.js              186 B  -> (yaprak)
  giris-sunucu.js       302 B  -> sablon.js, arsiv.js
  giris-tarayici.js     239 B  -> sablon.js, grafik.js
  grafik.js             458 B  -> birim.js
  sablon.js             229 B  -> bicim.js
  tarih.js              227 B  -> (yaprak)
-- degerlendirme sirasi (tarayici girisi) --
  tarih.js -> birim.js -> bicim.js -> sablon.js -> grafik.js -> giris-tarayici.js
-- giris kapanislari --
  giris-tarayici.js    6 modul   1726 B
  giris-sunucu.js      6 modul   1698 B
-- cikti parcalari --
  isteyen: giris-tarayici.js + giris-sunucu.js
     1029 B  bicim.js, birim.js, sablon.js, tarih.js
  isteyen: giris-tarayici.js
      697 B  giris-tarayici.js, grafik.js
  isteyen: giris-sunucu.js
      669 B  arsiv.js, giris-sunucu.js
-- toplam --
  kaynak: 2395 B   parcalar: 2395 B
  kopyalanan modul yok: true
```

Bayt sayıları dosyaların gerçek boyutlarıdır; kaynak dosyalar birebir yukarıdaki blokla
üretildiğinde bu değerler çıkar. Bir satırlık bir düzenleme bile sayıları değiştirir.

#### Değerlendirme Sırası

Çizgenin ilk kullanımı sıralamadır. Bir modülün gövdesi, bağımlılıklarının gövdesi
çalıştıktan sonra çalışmalıdır; aksi hâlde henüz tanımlanmamış bir bağa erişilir. Sonradan
gezinmeli dolaşım bu sırayı verir: `tarih.js` ve `birim.js` en başta, giriş noktası en
sonda. Bu, Veri Yapıları kursundaki topolojik sıralamanın çizgeye uygulanmış hâlidir.

Sıralamanın çıktıya iki yansıması vardır. Tek dosyaya yazılan bir parçada modüllerin
gövdeleri bu sırayla dizilir. Birden çok dosyaya bölünen çıktıda ise sıra, dosyaların
yükleme sırasına dönüşür: bir parça, bağımlı olduğu parça değerlendirilmeden
çalıştırılamaz.

Çizgede döngü varsa sonradan gezinme tek bir doğru sıra vermez. Modül sistemleri
konusundaki dairesel bağımlılık tartışması burada da geçerlidir: paketleyici döngüyü
kırmaz, yalnızca bir sıra seçer ve seçtiği sıra hangi bağın erken okunacağını belirler.

#### Giriş Noktası Kümesi

Bir derlemede kaç giriş noktası olacağı, oluşturma stratejisi kararının doğrudan
sonucudur. Tarayıcıda çalışan kod için bir giriş, sunucuda çalışan kod için ayrı bir
giriş, kenarda çalışan modül için üçüncü bir giriş gerekir. Her girişin kendi kapanışı
vardır ve kapanışlar kesişir: örnekte iki girişin toplam altışar modülünden dördü ortaktır.

Girişler yalnız kapsam olarak değil, **hedef** olarak da ayrılır. Aynı kaynaktan üretilen iki
çıktı farklı biçimlerde yazılır: sunucu çıktısı çalışma zamanının doğrudan yükleyebileceği modül
biçiminde, tarayıcı çıktısı belgeye eklenecek betik biçiminde. Dış bırakılan bağımlılıklar da
hedefe göre değişir — sunucu girişinde çalışma zamanının kendi sağladığı modüller çizgeye alınmaz;
tarayıcı girişinde böyle bir kaynak yoktur ve her şeyin çıktıya girmesi gerekir.

Bu, kaynak ağaçta tek bir dosyanın iki farklı çıktıda iki farklı biçimde görünebileceği
anlamına gelir. Ortak modüllerin her iki hedefte de çalışabilir olması, kaynak düzenine
konan bir kısıttır: ortak katmanda yalnız ortama bağımsız kod bulunur.

#### Parçalama Ölçütü

Ortak modüller iki yolla ele alınabilir. Her girişin çıktısına kopyalanabilirler; o zaman
aynı kod iki dosyada iki kez durur. Ya da ayrı bir parçaya konup her iki girişten
yüklenebilirler; o zaman bir dosya daha üretilir.

Karar, çizgeden okunabilen tek bir bilgiye dayanır: bir modülü hangi girişler ister. Bu
kümeye modülün **kullanım imzası (usage signature)** denir. Aynı imzaya sahip modüller
birlikte gerekir, dolayısıyla birlikte konabilir. Farklı imzalar farklı parçalar üretir.

Çıktıda üç parça görünmesinin nedeni budur: iki girişin de istediği dört modül tek bir
paylaşılan parçada, yalnız tarayıcının istediği iki modül bir parçada, yalnız sunucunun
istediği iki modül üçüncü parçada. Son satırdaki denetim ölçütün sağlamasıdır: parçaların
toplamı kaynağın toplamına eşitse hiçbir modül iki parçaya kopyalanmamıştır.

Uygulama Mimarisi kursundaki kod bölme dersi aynı ölçütü rota sınırları için kullanmıştı;
orada imzayı oluşturan küme rotalardı. Ölçüt aynıdır, imzayı üreten kaynak farklıdır: bu
derste girişler, orada rotalar.

#### Parçanın İçinde Ne Var

Bir parça, modül gövdelerinin arka arkaya yazılmasından ibaret değildir. Her modülün kendi
kapsamı korunmalı, dışa aktarımları başka modüllere görünür olmalı ve bir modül birden çok
kez içe aktarılsa bile bir kez değerlendirilmelidir. Bunu sağlayan üç şey parçanın içinde
yer alır: modül sarmalayıcıları, tek değerlendirmeyi güvenceye alan bir kayıt ve
belirteçleri çıktı anahtarlarına çeviren bir eşleme. Modüller, Araçlar ve Ekosistem
kursundaki paketleyici dersi bu yapıyı çalışır hâlde kurmuştu.

Paylaşılan parçanın varlığı bir şey daha ekler: parçalar arası dışa aktarım yüzeyi.
Tarayıcı parçası `sablon.js` içindeki iki adı kullanıyorsa, paylaşılan parça bu adları
dışarıya vermek zorundadır. Parça sınırı, bu yüzden yalnız bir dosya sınırı değil, aynı
zamanda bir arayüz sınırıdır: sınırı geçen her ad çıktıda görünür kalmalı, yeniden
adlandırılamaz veya elenemez.

Kapsam birleştirme bu noktada sınırlanır. Aynı parçanın içindeki modüller tek kapsamda
eritilebilir; parça sınırını geçen adlar eritilemez. Parça sayısını artırmak, bu yüzden
yalnız istek sayısını değil, uygulanabilecek dönüşümlerin alanını da etkiler.

#### Çizgenin Söylemediği Şeyler

Buradaki çizge yalnız statik içe aktarımlardan kuruldu ve bu, gerçek bir derlemenin
girdisinin bir parçasıdır.

Belirteçler düzenli ifadeyle bulundu. Bir dizginin içinde geçen içe aktarım yazımı da
eşleşir, yorumdaki bir satır da. Gerçek araçlar kaynağı ayrıştırıp soyut sözdizim ağacı
üzerinde çalışır; uzantı tamamlama, dizin girişleri, çıplak belirteç araması ve koşullu
girişlerin çözümü de o katmanın işidir.

Çizgeye giren tek kenar türü statik içe aktarım değildir. Dinamik içe aktarım bir kenardır ama
zamanı farklıdır; biçem dosyaları, görseller ve yazı tipleri de kenar üretir. Bunların her biri
parça oluşumunu değiştirir; dinamik içe aktarımı bir sonraki ders, varlık kenarlarını dördüncü
ders ekler.

Son olarak çizge, bir modülün *tamamının* gerekli olduğunu söyler; içindeki hangi adın
kullanıldığını söylemez. Paylaşılan parçadaki `birim.js` dört ad dışa aktarıyor, bunların
ikisi hiç kullanılmıyor olabilir. Bu ayrımı görmek için çizgeyi modül düzeyinden ad
düzeyine indirmek gerekir.

#### Özet

- Modül paketleme iki karardır: giriş noktalarından erişilen modül kümesinin çıkarılması
  ve bu kümenin çıktı parçalarına bölünmesi.
- Bağımlılık çizgesi giriş noktalarından dolaşımla kurulur; sonradan gezinmeli sıra,
  bağımlılıkları kendinden önce koyan değerlendirme sırasını verir.
- Giriş noktası sayısı oluşturma stratejisinden gelir; her girişin ayrı bir kapanışı, ayrı
  bir çıktı biçimi ve ayrı bir dış bağımlılık sınırı vardır.
- Modüller kullanım imzasına — kendilerini isteyen girişler kümesine — göre parçalara
  ayrılır; parçaların toplam boyutunun kaynağın toplamına eşit olması, hiçbir modülün
  kopyalanmadığının sağlamasıdır.
- Parça sınırı aynı zamanda bir arayüz sınırıdır: sınırı geçen adlar çıktıda korunmak
  zorundadır ve kapsam birleştirme parça içiyle sınırlıdır.

#### Sonraki Adım

Bu dersteki parçalar giriş noktalarından doğdu ve hepsi ilk yüklemede gerekliydi. Oysa bir
uygulamanın her parçası aynı anda gerekmez: istasyon sorumlusunun paneli yalnız o panel
açıldığında, dışa aktarma ekranı yalnız o düğmeye basıldığında ister. Kaynakta bu ayrımı
işaretleyen bir yazım vardır — `import()` çağrısı bir kenarı çizgeye ekler ama onu
zamanında ayırır. Sonraki ders bu sınırı ele alır: dinamik içe aktarımdan doğan parçalar
nasıl hesaplanır, ilk yüklemede inen ile sonradan inen nasıl ayrılır ve parça sayısını
artırmanın hangi noktada zarara döndüğü nasıl ölçülür.

### Kod Bölme

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/kod-bolme

Statik ve dinamik kenarların ayrılması; parça köklerinin belirlenmesi, ilk yüklemede inen ile sonradan inenin hesaplanması, istek başına maliyet ve aşırı bölünmüş parçaların birleştirilmesi.

Bir önceki dersteki parçalar giriş noktalarından doğdu ve hepsi ilk yüklemede gerekliydi.
Tarayıcı girişinin kapanışındaki altı modülün altısı da, kullanıcı henüz hiçbir şeye
dokunmadan indi. Oysa Kuzey Yamaç Ölçüm İstasyonu uygulamasında bu doğru değildir: ölçüm
listesi her ziyarette görünür, istasyon sorumlusunun paneli ise yalnız o panel açıldığında
gerekir. Panelin grafik çizimi ve dışa aktarma kodu, sayfayı okumaya gelen ziyaretçinin
indirdiği baytlar arasında bulunmamalıdır.

Bu ayrımı kaynakta işaretleyen bir yazım vardır. `import()` çağrısı bir bağımlılığı
çizgeye ekler ama onu ilk yüklemeden ayırır. Bu dersin sorusu, o işaretin çıktı parçalarını
nasıl değiştirdiği ve bölmenin nerede kâr getirip nerede zarara döndüğüdür.

#### İki Kenar Türü

Çizgede iki tür kenar vardır ve ayrım zamandadır.

**Statik kenar**, kaynağın en üstündeki içe aktarım bildirimidir. Modül sistemleri
konusunda kurulan kural gereği bu bildirimler modül değerlendirilmeden önce çözülür:
bağımlılık, kendisine bağlı olan modülden önce hazır olmak zorundadır. Statik kenarla
bağlanan iki modül aynı anda gerekir.

**Dinamik kenar**, `import()` çağrısıdır. Çağrı bir söz döndürür; bağımlılık, çağrı
yürütülene kadar yüklenmez. Dinamik kenarla bağlanan iki modül aynı anda gerekmez ve
aralarında bir zaman sınırı vardır.

Bu zaman sınırı, çıktı tarafında bir dosya sınırına dönüşür. Dinamik içe aktarımın hedefi,
kendi bağımlılıklarıyla birlikte ayrı bir dosyaya konabilir; o dosya, çağrı yürütüldüğünde
indirilir. Böyle bir hedefe **parça kökü (chunk root)** denir. Bir derlemede parça kökleri
kümesi, giriş noktaları ile dinamik içe aktarım hedeflerinin birleşimidir.

#### Panelin Ayrılması

Kaynak ağaca panel ve bağımlılıkları eklenir; tarayıcı girişi grafiği doğrudan içe
aktarmak yerine paneli dinamik olarak ister. Aşağıdaki blok, bir önceki dersin kaynak
ağacının üzerine yazar.

Aşağıdaki blok, Modül Paketleme dersinde kurulan `kaynak/` ağacının üzerine yazar; betiği çalıştırmadan önce o dersin kurulum bloğu bu dizinde çalıştırılmış olmalıdır.

```bash
mkdir -p kaynak

cat > kaynak/uyari.js <<'EOF'
import { SICAKLIK_ALT, SICAKLIK_UST } from "./birim.js";

export const esikAsimi = (olcumler) =>
  olcumler.filter((o) => o.sicaklik < SICAKLIK_ALT || o.sicaklik > SICAKLIK_UST);
EOF

cat > kaynak/disa-aktarma.js <<'EOF'
import { ikiliBirim } from "./bicim.js";
import { gunAnahtari } from "./tarih.js";

const kacir = (a) => (a.includes(",") ? "\"" + a + "\"" : a);

export function csvUret(olcumler) {
  const basliklar = ["gun", "damga", "sicaklik", "nem"];
  const satirlar = olcumler.map((o) =>
    [gunAnahtari(o.damga), o.damga, ikiliBirim(o), o.nem].map(kacir).join(","));
  return [basliklar.join(","), ...satirlar].join("\n");
}
EOF

cat > kaynak/panel.js <<'EOF'
import { sicaklikYolu } from "./grafik.js";
import { csvUret } from "./disa-aktarma.js";
import { baslik } from "./sablon.js";

export async function paneliCiz(kok, istasyon, olcumler) {
  const { esikAsimi } = await import("./uyari.js");
  const asim = esikAsimi(olcumler);
  kok.innerHTML = baslik(istasyon + " paneli") +
    "<svg><path d=\"" + sicaklikYolu(olcumler, 320, 120) + "\"/></svg>" +
    "<p>" + asim.length + " asim</p>";
  return { csv: () => csvUret(olcumler) };
}
EOF

cat > kaynak/giris-tarayici.js <<'EOF'
import { liste, baslik } from "./sablon.js";

export function baslat(kok, istasyon, olcumler) {
  kok.innerHTML = baslik(istasyon) + liste(olcumler);
  return {
    async paneliAc(panelKok) {
      const { paneliCiz } = await import("./panel.js");
      return paneliCiz(panelKok, istasyon, olcumler);
    },
  };
}
EOF
```

İki dinamik sınır oluştu: giriş noktası paneli, panel de eşik uyarısı modülünü dinamik
olarak istiyor. İkincisi iç içe bir sınırdır ve maliyeti ayrıca ölçülecek.

#### Parça Köklerinden Parçalara

Bir önceki dersin imza ölçütü aynen geçerlidir, yalnız imzayı üreten küme değişir: giriş
noktaları yerine parça kökleri. Bir modülün imzası, onu **statik olarak** erişebilen
kökler kümesidir. İmzasında giriş noktası bulunan modül ilk yüklemede iner; bulunmayan
modül, imzasındaki köklerden biri istendiğinde iner.

```js
// bolme.mjs — statik ve dinamik kenarlardan parca cizgesi, maliyet ve birlestirme.
import { readFileSync, statSync } from "node:fs";
import { dirname, join, relative } from "node:path";

const GIRIS = "kaynak/giris-tarayici.js";
const STATIK = /^\s*(?:import|export)[^"']*from\s*["']([^"']+)["']/gm;
const DINAMIK = /\bimport\(\s*["']([^"']+)["']\s*\)/g;

const cizge = new Map();                        // yol -> { boyut, statik[], dinamik[] }
const kuyruk = [GIRIS];
while (kuyruk.length > 0) {
  const yol = kuyruk.shift();
  if (cizge.has(yol)) continue;
  const kaynak = readFileSync(yol, "utf8");
  const coz = (e) => [...kaynak.matchAll(e)].map(([, b]) => relative(".", join(dirname(yol), b)));
  const statik = coz(STATIK), dinamik = coz(DINAMIK);
  cizge.set(yol, { boyut: statSync(yol).size, statik, dinamik });
  kuyruk.push(...statik, ...dinamik);
}

// Parca koku: giris noktasi ve her dinamik ice aktarim hedefi.
const kokler = [GIRIS, ...[...cizge.values()].flatMap((d) => d.dinamik)];
const kapanis = new Map(kokler.map((kok) => {
  const gorulen = new Set(), yigit = [kok];
  while (yigit.length > 0) {
    const y = yigit.pop();
    if (gorulen.has(y)) continue;
    gorulen.add(y);
    yigit.push(...cizge.get(y).statik);
  }
  return [kok, gorulen];
}));

// Imza: modulu statik olarak isteyen kokler kumesi. Ayni imza = ayni parca.
const gruplar = new Map();
for (const yol of cizge.keys()) {
  const anahtar = kokler.filter((k) => kapanis.get(k).has(yol)).join("|");
  if (!gruplar.has(anahtar)) gruplar.set(anahtar, []);
  gruplar.get(anahtar).push(yol);
}

const ad = (y) => y.replace("kaynak/", "").replace(".js", "");
const bayt = (ms) => ms.reduce((t, y) => t + cizge.get(y).boyut, 0);
let parcalar = [...gruplar].map(([anahtar, moduller], i) => ({
  ad: "P" + (i + 1),
  kokler: anahtar.split("|"),
  moduller,
  boyut: bayt(moduller),
  ilk: anahtar.split("|").includes(GIRIS),
}));

const RTT = 60, BANT = 192;                     // ms; B/ms (1.5 Mbit/s)
const maliyet = (liste) => liste.length * RTT + liste.reduce((t, p) => t + p.boyut, 0) / BANT;
const eszamansiz = (liste, kok) => liste.filter((p) => p.kokler.includes(kok) && !p.ilk);

function rapor(baslik, liste) {
  console.log(baslik);
  for (const p of liste.sort((a, b) => a.ad.localeCompare(b.ad))) {
    console.log("  " + p.ad + "  " + String(p.boyut).padStart(4) + " B  " +
      (p.ilk ? "ilk        " : "eszamansiz ") + p.moduller.map(ad).sort().join(", "));
  }
  const ilkListe = liste.filter((p) => p.ilk);
  console.log("  ilk yukleme      " + ilkListe.length + " istek  " +
    String(ilkListe.reduce((t, p) => t + p.boyut, 0)).padStart(4) + " B  " +
    maliyet(ilkListe).toFixed(1).padStart(6) + " ms");
  for (const kok of kokler.slice(1)) {
    const g = eszamansiz(liste, kok);
    if (g.length === 0) continue;
    console.log("  " + (ad(kok) + " siniri").padEnd(17) + g.length + " istek  " +
      String(g.reduce((t, p) => t + p.boyut, 0)).padStart(4) + " B  " +
      maliyet(g).toFixed(1).padStart(6) + " ms");
  }
}

console.log("-- cizge --");
for (const [yol, d] of [...cizge].sort()) {
  console.log("  " + ad(yol).padEnd(16) + String(d.boyut).padStart(4) + " B  statik: " +
    (d.statik.map(ad).join(", ") || "-") + "  dinamik: " + (d.dinamik.map(ad).join(", ") || "-"));
}
console.log("  parca koku: " + kokler.map(ad).join(", "));

rapor("-- imzaya gore parcalar (RTT " + RTT + " ms, bant 1.5 Mbit/s) --", parcalar);

// 1. Girisle birlikte inen parcalar ayri tutulmaz: hepsi tek ilk parcada birlesir.
const ilkler = parcalar.filter((p) => p.ilk);
if (ilkler.length > 1) {
  const birlesik = {
    ad: "I1", kokler: [...new Set(ilkler.flatMap((p) => p.kokler))],
    moduller: ilkler.flatMap((p) => p.moduller), boyut: bayt(ilkler.flatMap((p) => p.moduller)),
    ilk: true,
  };
  parcalar = [birlesik, ...parcalar.filter((p) => !p.ilk)];
  console.log("-- birlestirme: " + ilkler.map((p) => p.ad).join(" + ") + " -> I1 (hepsi ilk yuklemede iniyor)");
}

// 2. Esigin altinda kalan ve tek yerden istenen eszamansiz parca, isteyenin parcasina girer.
const ESIK = 300;
for (const p of [...parcalar]) {
  if (p.ilk || p.boyut >= ESIK || p.kokler.length !== 1) continue;
  const isteyen = [...cizge].filter(([, d]) => d.dinamik.includes(p.kokler[0])).map(([y]) => y);
  const hedefler = [...new Set(isteyen.map((y) => parcalar.find((q) => q.moduller.includes(y))))];
  if (hedefler.length !== 1 || hedefler[0].ilk) continue;
  const hedef = hedefler[0];
  hedef.moduller.push(...p.moduller);
  hedef.boyut += p.boyut;
  parcalar = parcalar.filter((q) => q !== p);
  console.log("-- birlestirme: " + p.ad + " (" + p.boyut + " B < " + ESIK +
    " B, tek isteyen) -> " + hedef.ad);
}

rapor("-- birlestirme sonrasi --", parcalar);
```

```
$ node bolme.mjs
-- cizge --
  bicim            387 B  statik: tarih, birim  dinamik: -
  birim            186 B  statik: -  dinamik: -
  disa-aktarma     418 B  statik: bicim, tarih  dinamik: -
  giris-tarayici   316 B  statik: sablon  dinamik: panel
  grafik           458 B  statik: birim  dinamik: -
  panel            482 B  statik: grafik, disa-aktarma, sablon  dinamik: uyari
  sablon           229 B  statik: bicim  dinamik: -
  tarih            227 B  statik: -  dinamik: -
  uyari            179 B  statik: birim  dinamik: -
  parca koku: giris-tarayici, panel, uyari
-- imzaya gore parcalar (RTT 60 ms, bant 1.5 Mbit/s) --
  P1   316 B  ilk        giris-tarayici
  P2   843 B  ilk        bicim, sablon, tarih
  P3  1358 B  eszamansiz disa-aktarma, grafik, panel
  P4   179 B  eszamansiz uyari
  P5   186 B  ilk        birim
  ilk yukleme      3 istek  1345 B   187.0 ms
  panel siniri     1 istek  1358 B    67.1 ms
  uyari siniri     1 istek   179 B    60.9 ms
-- birlestirme: P1 + P2 + P5 -> I1 (hepsi ilk yuklemede iniyor)
-- birlestirme: P4 (179 B < 300 B, tek isteyen) -> P3
-- birlestirme sonrasi --
  I1  1345 B  ilk        bicim, birim, giris-tarayici, sablon, tarih
  P3  1537 B  eszamansiz disa-aktarma, grafik, panel, uyari
  ilk yukleme      1 istek  1345 B    67.0 ms
  panel siniri     1 istek  1537 B    68.0 ms
```

#### Çıktının Okunması

İlk tablo ham imza gruplamasını verir. Panelin kendisi, grafiği ve dışa aktarma modülü tek
bir eşzamansız parçada toplanmıştır: üçünü de yalnız panel kökü ister. Şablon, biçim ve
tarih modülleri hem giriş noktasından hem panelden erişilebildiği için ilk yüklemededir —
paneli bekletmek için bir gerekçe yoktur, zaten inmişlerdir.

Bölmenin kazancı ilk satırdadır: toplam 2882 baytın 1345'i ilk yüklemede iner. Panelin
1537 baytı, o panel hiç açılmazsa hiç inmez. Oran küçük bir örnekte gösteriştir; gerçek bir
uygulamada grafik çizimi ve dışa aktarma gibi modüllerin ağırlığı, ilk yüklemenin birkaç
katı olabilir.

`birim.js` modülünün ayrı bir parçaya düşmesi imza ölçütünün doğrudan sonucudur: onu üç
kök de ister, dolayısıyla imzası diğerlerinden farklıdır. Ölçüt bu ayrımı doğru yapar ama
sonucu her zaman yararlı değildir; bir sonraki bölüm bunu düzeltir.

#### Aşırı Bölmenin Maliyeti

Her parça bir istektir. İstek başına gecikme, taşınan baytın süresinden büyük olabilir;
maliyet satırları bu iki kalemi ayrı ayrı toplar. İlk yüklemede üç parça 187 milisaniye
tutar, oysa aynı baytlar tek dosyada 67 milisaniyede iner. Aradaki 120 milisaniye, hiçbir
şey kazandırmayan iki fazladan istektir.

Buradan iki birleştirme kuralı çıkar.

**Birlikte inen parçalar ayrı tutulmaz.** İmzasında giriş noktası bulunan her parça ilk
yüklemede iner; bunları ayrı dosyalarda tutmanın tek etkisi istek sayısını artırmaktır. Üç
parça tek bir ilk parçada birleşir ve ilk yükleme 187 milisaniyeden 67 milisaniyeye iner.
Bu kuralın bir istisnası vardır ve önbelleklemeyle ilgilidir: iki farklı giriş noktasının
paylaştığı bir parça — bir önceki dersteki tarayıcı ve sunucu ortaklığı gibi — ayrı
tutulduğunda iki hedefte de bir kez indirilir. Aynı gerekçe sürümler arasında da geçerlidir
ve beşinci derste ölçülecektir.

**Eşiğin altında kalan parça ayrılmaz.** Eşik uyarısı modülü 179 bayttır ve tek bir yerden
istenir. Ayrı bir dosya olarak tutulduğunda kendisine 60,9 milisaniyelik bir istek
maliyeti ödetir; panelin parçasına katıldığında ise panelin indirmesini 67,1 milisaniyeden
68,0 milisaniyeye çıkarır. Fark tek yönlüdür: bölme, kazandırdığından fazlasını götürür.

İç içe dinamik sınırın gizli bir maliyeti daha vardır. Eşik uyarısı, panel yüklendikten
**sonra** istenir; iki istek sıralıdır. Kullanıcı paneli açtığında bekleme 67,1 + 60,9 =
128 milisaniyedir, birleştirmeden sonra 68,0 milisaniye. Sıralı istek zinciri, oluşturma
modelleri konusunda istemcide oluşturmanın maliyeti olarak ölçülen aynı olgudur; parça
düzeyinde de aynı biçimde çalışır.

#### Sınır Nereye Konur

Bölme sınırı koymanın ölçütü dört sorudur ve dördü de ölçülebilir.

**İlk ekranda gerekli mi?** Gerekliyse bölünmez; bölme yalnız gecikme ekler. Panel için
yanıt hayırdır, ölçüm listesi için evet.

**Kaçınılan bayt, eklenen isteği karşılıyor mu?** Karşılamıyorsa bölünmez. Örnekteki eşik
300 bayttır ve gösterim içindir; gerçek bir eşik, ölçülen istek gecikmesi ile bant
genişliğinden hesaplanır.

**Kullanıcı eyleminin doğal bir beklemesi var mı?** Bir düğmeye basıldıktan sonra kısa bir
bekleme kabul edilebilir; sayfa ilk açılırken beklemek kabul edilmez. Bölme sınırları
eylem sınırlarına yerleştirilir.

**Kod ne sıklıkla isteniyor?** Ziyaretçilerin çoğunun açtığı bir ekranın kodunu
geciktirmek, ilk yüklemeden kazanılanı gezinmede fazlasıyla geri verir. Uygulama Mimarisi
kursundaki kod bölme dersi bu takası rota sınırlarında ele almış ve önceden getirmeyi
çözüm olarak koymuştu; ölçüt aynıdır, sınırın kaynağı farklıdır — orada rota tanımı, burada
`import()` çağrısının bulunduğu yer.

#### Bölmenin Getirdiği Yükümlülükler

Bölünmüş bir çıktı, tek dosyalı bir çıktının taşımadığı üç yükümlülük getirir.

**Parça adları çalışma zamanında çözülmelidir.** Giriş parçası, `import()` çağrısı
yürütüldüğünde hangi dosyayı isteyeceğini bilmek zorundadır. Bu bilgi çıktıya bir eşleme
olarak yazılır; beşinci ders bu eşlemeyi içerik karmalı adlarla birlikte kuracak.

**Bir parçanın yokluğu çalışma zamanı hatasıdır.** Kullanıcı sayfayı açıkken yeni bir
sürüm yayımlanırsa, eski sayfanın isteyeceği parça adları sunucuda bulunmayabilir. Bu,
dağıtım konusundaki geri alma dersinin doğrudan konusudur ve çözümü eski sürümün
dosyalarını bir süre daha yayında tutmaktır.

**Yükleme sırası korunmalıdır.** Bir eşzamansız parça, bağımlı olduğu ilk parça
değerlendirilmeden çalıştırılamaz. Çıktıdaki yükleyici bu bağı izler; parça sayısı
arttıkça izlenecek bağ sayısı da artar.

#### Özet

- Çizgedeki iki kenar türü zamanla ayrılır: statik içe aktarım aynı anda gerekliliği,
  `import()` ise zaman sınırı olan bir bağımlılığı bildirir.
- Parça kökleri kümesi giriş noktaları ile dinamik içe aktarım hedeflerinin birleşimidir;
  imzasında giriş noktası bulunan modül ilk yüklemede, bulunmayan modül kökü istendiğinde
  iner.
- Örnekte toplam 2882 baytın 1345'i ilk yüklemede iniyor; panelin 1537 baytı yalnız panel
  açılırsa indiriliyor.
- Her parça bir istektir: birlikte inen parçaları ayrı tutmak ilk yüklemeyi 67
  milisaniyeden 187 milisaniyeye çıkarır, eşiğin altındaki bir parçayı ayırmak ise
  kazandırdığından fazlasını götürür.
- İç içe dinamik sınırlar istekleri sıralı hâle getirir; iki sınır tek parçada
  birleştirildiğinde bekleme 128 milisaniyeden 68 milisaniyeye iner.
- Bölme sınırı dört ölçütle konur: ilk ekranda gereklilik, kaçınılan baytın istek
  maliyetini karşılaması, eylemin doğal beklemesi ve kodun istenme sıklığı.

#### Sonraki Adım

Parçalar artık hem sayıca hem zamanca doğru ayrılmış durumda. Ama parçaların içeriği hâlâ
modül düzeyinde belirleniyor: çizge bir modülün *gerekli* olduğunu söylüyor, içindeki
hangi adın kullanıldığını söylemiyor. Paylaşılan parçadaki birim modülü dört ad dışa
aktarıyor ve bunların hepsi kullanılmıyor; biçimlendirme modülünün ikinci işlevi yalnız
dışa aktarma yolunda çağrılıyor. Sonraki ders çizgeyi modül düzeyinden ad düzeyine indirir:
kullanılmayan dışa aktarımların elenmesi, bu elemenin hangi durumlarda yapılamadığı ve
paketin bunu bildirimle nasıl mümkün kıldığı.

### Ağaç Sarsma

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/agac-sarsma

Çizgenin modül düzeyinden ad düzeyine indirilmesi; canlı dışa aktarımların yayılımı, yan etkili modülün elenememesi, paket bildirimindeki yan etki bildiriminin üç ayarı ve elemeyi engelleyen kaynak yazımları.

Bir önceki ders parçaların hangi modülleri taşıyacağını belirledi. Belirlemediği şey, o
modüllerin *içinde* nelerin taşınacağıdır. Bağımlılık çizgesi bir modülün gerekli olduğunu
söyler; içindeki hangi adın kullanıldığını söylemez. Kuzey Yamaç Ölçüm İstasyonu
uygulamasının birim modülü altı ad dışa aktarıyorsa ve bunların ikisi hiçbir yerden
çağrılmıyorsa, çizge bunu göremez.

**Ağaç sarsma (tree shaking)**, çizgeyi modül düzeyinden ad düzeyine indirip erişilemeyen
dışa aktarımları çıktıdan düşürme işidir. Modüller, Araçlar ve Ekosistem kursundaki
paketleyici dersi bu dönüşümün üç koşulunu saymıştı. Bu ders koşulları çalıştırarak
sınar: birinin ihlal edildiği bir kaynak ağaç kurar, elemenin ne zaman durduğunu ölçer ve
paketin bunu bildirimle nasıl mümkün kıldığını gösterir.

#### Ad Düzeyinde Canlılık

Çizgede düğümler modüller değil, `modül#ad` çiftleridir. Bir çift **canlı** ise çıktıda
kalır; değilse düşer.

Yayılım giriş noktasından başlar. Giriş noktasının dışa aktarımları koşulsuz canlıdır;
onları isteyen şey derlemenin dışındadır. Canlı bir dışa aktarımın gövdesinde geçen her ad
da canlıdır: gövde bir içe aktarım adına dokunuyorsa, o adın kaynak modüldeki karşılığı
canlanır. Yeniden dışa aktarımlar zinciri uzatır — toplayıcı bir dosyadaki
`export { a } from "./x.js"` satırı, `a` istendiğinde `x.js` içindeki `a` adını canlandırır,
ötekilere dokunmaz.

Dinamik içe aktarım bu zincirin bir yerinde belirsizlik yaratır. `import()` çağrısının
sonucu bir ad alanı nesnesidir ve hangi adların kullanılacağı çağrı yerinden her zaman
okunamaz. Temkinli davranış, dinamik hedefin bütün dışa aktarımlarını canlı saymaktır.

#### Kaynak Ağacın Gerçek Yüzeyi

Şimdiye kadarki kaynak ağaç yalnız kullanılan adları taşıyordu. Gerçek bir yardımcı modül
kümesi böyle olmaz: tüketicilerinin hepsinin ihtiyaç duymadığı bir yüzey sunar, adlar tek
bir toplayıcı dosyadan dağıtılır ve içlerinden biri en üst düzeyde iş yapar.

Aşağıdaki blok, Modül Paketleme ve Kod Bölme derslerinde kurulan `kaynak/` ağacını sürdürür; giriş noktası oradan gelir ve o derslerin kurulum blokları önce çalıştırılmalıdır.

```bash
mkdir -p kaynak

cat > kaynak/birim.js <<'EOF'
export const SICAKLIK_ALT = -60;
export const SICAKLIK_UST = 60;
export const sinirdaMi = (c) => c >= SICAKLIK_ALT && c <= SICAKLIK_UST;
export const fahrenhayt = (c) => c * 9 / 5 + 32;
export const milimetreCiva = (hpa) => hpa * 0.75006157584566;
export const ruzgarSogumasi = (c, hiz) =>
  13.12 + 0.6215 * c - 11.37 * hiz ** 0.16 + 0.3965 * c * hiz ** 0.16;
EOF

cat > kaynak/tarih.js <<'EOF'
const iki = (s) => String(s).padStart(2, "0");
export const saatDakika = (d) => iki(new Date(d).getUTCHours()) + ":" + iki(new Date(d).getUTCMinutes());
export const gunAnahtari = (d) => new Date(d).toISOString().slice(0, 10);
export const haftaAnahtari = (d) => {
  const t = new Date(d);
  const gun = Math.floor((t - Date.UTC(t.getUTCFullYear(), 0, 1)) / 86400000);
  return t.getUTCFullYear() + "-H" + iki(Math.ceil((gun + 1) / 7));
};
EOF

cat > kaynak/tahmin.js <<'EOF'
import { SICAKLIK_ALT, SICAKLIK_UST } from "./birim.js";

const gecerli = (o) => o.sicaklik > SICAKLIK_ALT && o.sicaklik < SICAKLIK_UST;

export const egilim = (olcumler) => {
  const dizi = olcumler.filter(gecerli);
  if (dizi.length < 2) return 0;
  return (dizi.at(-1).sicaklik - dizi[0].sicaklik) / (dizi.length - 1);
};
EOF

cat > kaynak/kayit.js <<'EOF'
export const bicimciler = new Map();
export const kaydet = (ad, islev) => bicimciler.set(ad, islev);
EOF

cat > kaynak/olcek-kaydi.js <<'EOF'
import { kaydet } from "./kayit.js";
import { fahrenhayt } from "./birim.js";

kaydet("fahrenhayt", fahrenhayt);
EOF

cat > kaynak/arac.js <<'EOF'
import "./olcek-kaydi.js";
export { sinirdaMi, fahrenhayt, milimetreCiva, ruzgarSogumasi } from "./birim.js";
export { saatDakika, gunAnahtari, haftaAnahtari } from "./tarih.js";
export { egilim } from "./tahmin.js";
export { bicimciler } from "./kayit.js";
EOF

cat > kaynak/bicim.js <<'EOF'
import { saatDakika, sinirdaMi, fahrenhayt, bicimciler } from "./arac.js";

export function olcumSatiri(o) {
  const uyari = sinirdaMi(o.sicaklik) ? "" : " (sinir disi)";
  const ikinci = bicimciler.has("fahrenhayt")
    ? " / " + bicimciler.get("fahrenhayt")(o.sicaklik).toFixed(1) + " F" : "";
  return saatDakika(o.damga) + "  " + o.sicaklik.toFixed(1) + " C" + ikinci + "  %" + o.nem + uyari;
}
export const ikiliBirim = (o) => o.sicaklik.toFixed(1) + " C / " + fahrenhayt(o.sicaklik).toFixed(1) + " F";
EOF

cat > paket.json <<'EOF'
{
  "name": "kuzey-yamac",
  "type": "module",
  "sideEffects": ["./kaynak/olcek-kaydi.js"]
}
EOF
```

Üç yapı eklendi. Birim ve tarih modülleri kullanılmayan adlar taşıyor. Toplayıcı dosya
adları tek yerden dağıtıyor ve içine bir de çıplak içe aktarım koyuyor. Ölçek kaydı modülü
hiçbir şey dışa aktarmıyor; yalnız en üst düzeyde bir kayda yazıyor ve biçimlendirme
modülü çıktısını o kayda bakarak değiştiriyor.

#### Çözümleyici

Aşağıdaki betik modülleri satır satır ayrıştırır, canlılığı yayar ve üç ayrı yan etki
politikası altında çıktı boyutunu hesaplar.

```js
// sarsma.mjs — ad duzeyinde canlilik cozumlemesi ve uc yan etki politikasi.
import { readFileSync } from "node:fs";
import { dirname, join, relative } from "node:path";

const GIRIS = "kaynak/giris-tarayici.js";
const BASLANGIC = /^(import\b|export\b|const\b|let\b|function\b|class\b)/;

function ayristir(yol) {
  const satirlar = readFileSync(yol, "utf8").split("\n");
  const coz = (b) => relative(".", join(dirname(yol), b));
  const iceAktarim = new Map();          // yerel ad -> { modul, dis ad }
  const ciplak = [];                     // yalniz yan etki icin ice aktarilan moduller
  const parca = [];                      // { ad, modul, dis, govde, disaAktarim }
  for (let i = 0; i < satirlar.length; i++) {
    const s = satirlar[i];
    let e;
    if ((e = s.match(/^import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/))) {
      for (const p of e[1].split(",").map((x) => x.trim()).filter(Boolean)) {
        const [dis, yerel = dis] = p.split(/\s+as\s+/);
        iceAktarim.set(yerel, { modul: coz(e[2]), dis });
      }
    } else if ((e = s.match(/^import\s*["']([^"']+)["']/))) {
      ciplak.push(coz(e[1]));
    } else if ((e = s.match(/^export\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/))) {
      for (const p of e[1].split(",").map((x) => x.trim()).filter(Boolean)) {
        parca.push({ ad: p, modul: coz(e[2]), dis: p, govde: "", disaAktarim: true,
          boyut: Buffer.byteLength(p) + 2 });
      }
    } else if ((e = s.match(/^(?:export\s+)?(?:const|let|function|async function|class)\s+(\w+)/))) {
      const govde = [s];
      while (i + 1 < satirlar.length && !BASLANGIC.test(satirlar[i + 1])) govde.push(satirlar[++i]);
      const metin = govde.join("\n").replace(/\s+$/, "");
      parca.push({ ad: e[1], govde: metin, disaAktarim: s.startsWith("export"),
        boyut: Buffer.byteLength(metin) + 1 });
    } else if (s.trim() && !s.trim().startsWith("//")) {
      parca.push({ ad: null, govde: s, disaAktarim: false, boyut: Buffer.byteLength(s) + 1 });
    }
  }
  return { iceAktarim, ciplak, parca, boyut: Buffer.byteLength(readFileSync(yol)) };
}

const modul = new Map();
const kuyruk = [GIRIS];
while (kuyruk.length > 0) {
  const yol = kuyruk.shift();
  if (modul.has(yol)) continue;
  const m = ayristir(yol);
  modul.set(yol, m);
  const dinamik = [...readFileSync(yol, "utf8").matchAll(/\bimport\(\s*["']([^"']+)["']\s*\)/g)]
    .map(([, b]) => relative(".", join(dirname(yol), b)));
  kuyruk.push(...[...m.iceAktarim.values()].map((i) => i.modul), ...m.ciplak, ...dinamik,
    ...m.parca.filter((p) => p.modul).map((p) => p.modul));
  m.dinamik = dinamik;
}

function coz(politika) {                 // politika: "yok" | "yansiz" | "liste"
  const LISTE = JSON.parse(readFileSync("paket.json", "utf8")).sideEffects;
  const canli = new Set();               // "modul#ad"
  const tutulan = new Set();
  const is = [];

  function govdeyiTara(yol, govde) {
    const m = modul.get(yol);
    for (const [yerel, kaynak] of m.iceAktarim)
      if (new RegExp("\\b" + yerel + "\\b").test(govde)) is.push([kaynak.modul, kaynak.dis]);
    for (const p of m.parca)
      if (p.ad && new RegExp("\\b" + p.ad + "\\b").test(govde) && !govde.startsWith("export"))
        is.push([yol, p.ad]);
  }

  function tut(yol) {
    if (tutulan.has(yol)) return;
    tutulan.add(yol);
    const m = modul.get(yol);
    for (const p of m.parca) if (!p.disaAktarim) govdeyiTara(yol, p.govde);
    for (const c of m.ciplak) {
      const korunur = politika === "yok" || (politika === "liste" && LISTE.includes("./" + c));
      if (korunur) tut(c);
    }
    for (const d of m.dinamik) {
      tut(d);
      for (const p of modul.get(d).parca) if (p.disaAktarim) is.push([d, p.ad]);
    }
    if (politika === "yok") {
      for (const i of m.iceAktarim.values()) tut(i.modul);
      for (const p of m.parca) if (p.modul) tut(p.modul);
    }
  }

  function canlandir(yol, ad) {
    const anahtar = yol + "#" + ad;
    if (canli.has(anahtar)) return;
    canli.add(anahtar);
    tut(yol);
    const p = modul.get(yol).parca.find((x) => x.ad === ad && x.disaAktarim);
    if (!p) return;
    if (p.modul) is.push([p.modul, p.dis]);
    else govdeyiTara(yol, p.govde);
  }

  tut(GIRIS);
  for (const p of modul.get(GIRIS).parca) if (p.disaAktarim) is.push([GIRIS, p.ad]);
  while (is.length > 0) canlandir(...is.pop());

  let toplam = 0;
  const dusen = [], oluAd = [];
  for (const [yol, m] of modul) {
    if (!tutulan.has(yol)) { dusen.push(yol); continue; }
    let bayt = m.boyut;
    for (const p of m.parca)
      if (p.disaAktarim && !canli.has(yol + "#" + p.ad)) {
        bayt -= p.boyut;
        oluAd.push(yol.replace("kaynak/", "") + "#" + p.ad);
      }
    toplam += bayt;
  }
  return { toplam, dusen, oluAd };
}

const ham = [...modul.values()].reduce((t, m) => t + m.boyut, 0);
console.log("cizgedeki modul:", modul.size, " ham boyut:", ham, "B");
for (const politika of ["yok", "yansiz", "liste"]) {
  const r = coz(politika);
  console.log("-- sideEffects: " + politika + " --");
  console.log("  cikti boyutu : " + r.toplam + " B  (kazanc " + (ham - r.toplam) + " B)");
  console.log("  dusen modul  : " + (r.dusen.map((y) => y.replace("kaynak/", "")).join(", ") || "-"));
  console.log("  olu ad       : " + (r.oluAd.sort().join(", ") || "-"));
}
```

```
$ node sarsma.mjs
cizgedeki modul: 13  ham boyut: 4188 B
-- sideEffects: yok --
  cikti boyutu : 3546 B  (kazanc 642 B)
  dusen modul  : -
  olu ad       : arac.js#egilim, arac.js#gunAnahtari, arac.js#haftaAnahtari, arac.js#milimetreCiva, arac.js#ruzgarSogumasi, birim.js#milimetreCiva, birim.js#ruzgarSogumasi, tahmin.js#egilim, tarih.js#haftaAnahtari
-- sideEffects: yansiz --
  cikti boyutu : 3231 B  (kazanc 957 B)
  dusen modul  : olcek-kaydi.js, tahmin.js
  olu ad       : arac.js#egilim, arac.js#gunAnahtari, arac.js#haftaAnahtari, arac.js#milimetreCiva, arac.js#ruzgarSogumasi, birim.js#milimetreCiva, birim.js#ruzgarSogumasi, kayit.js#kaydet, tarih.js#haftaAnahtari
-- sideEffects: liste --
  cikti boyutu : 3408 B  (kazanc 780 B)
  dusen modul  : tahmin.js
  olu ad       : arac.js#egilim, arac.js#gunAnahtari, arac.js#haftaAnahtari, arac.js#milimetreCiva, arac.js#ruzgarSogumasi, birim.js#milimetreCiva, birim.js#ruzgarSogumasi, tarih.js#haftaAnahtari
```

Üç politika, aynı kaynak ağaçtan üç ayrı çıktı üretiyor. Ölü ad listesi hepsinde ortak bir
çekirdek taşıyor: milimetre cıva, rüzgâr soğuması, hafta anahtarı ve eğilim hesabı hiçbir
canlı gövdede geçmiyor. Toplayıcı dosyadaki yeniden dışa aktarım satırları da onlarla
birlikte düşüyor — adı taşıyan satır, adın kendisi ölünce anlamsızdır.

#### Yan Etki Engeli

Politikalar arasındaki farkı yaratan tek dosya ölçek kaydıdır. Bu modülün dışa aktarımı
yoktur; çizgeye yalnız çıplak bir içe aktarımla girer ve en üst düzeyinde bir işlev
çağırır. Ad düzeyi çözümlemesi burada tıkanır: canlanacak bir ad yoktur, ama modülün
çalışması bir başkasının davranışını değiştirir.

Sağlaması bir çalıştırmayla yapılır. Aşağıdaki blok, elemenin uygulandığı bir kopya
üretir ve aynı çağrıyı iki çıktı üzerinde koşturur. `sed -i` seçeneği BSD ve GNU
sürümlerinde farklı davranır; yedek uzantısı vererek her ikisinde de aynı sonuç alınır.

```bash
#!/usr/bin/env bash
# Yan etkili modul elenmis bir kopya uretilir, ayni cagri iki cikti uzerinde kosturulur.
rm -rf elenmis && mkdir elenmis && cp kaynak/*.js elenmis/
sed -i.yedek -e '/^import ".\/olcek-kaydi.js";$/d' -e '/from ".\/tahmin.js"/d' elenmis/arac.js
rm -f elenmis/*.yedek elenmis/olcek-kaydi.js elenmis/tahmin.js

OLCUM='{ damga: "2026-02-11T06:00:00Z", sicaklik: -4.2, nem: 72 }'
node --input-type=module -e "
import { olcumSatiri } from './kaynak/bicim.js';
console.log('korunmus:', olcumSatiri(${OLCUM}));
"
node --input-type=module -e "
import { olcumSatiri } from './elenmis/bicim.js';
console.log('elenmis :', olcumSatiri(${OLCUM}));
"
```

```
korunmus: 06:00  -4.2 C / 24.4 F  %72
elenmis : 06:00  -4.2 C  %72
```

İkinci satırda Fahrenhayt sütunu yoktur. Eleme bir hata üretmedi, bir istisna fırlatmadı;
çıktıyı sessizce değiştirdi. Bu, ağaç sarsmanın en tehlikeli kusur biçimidir: derleme
başarılı görünür, testlerin kapsamadığı bir davranış kaybolur.

Bu yüzden araç, yan etki durumu bildirilmemiş bir modülü kendiliğinden düşürmez.
Bilgiyi paketin bildirimindeki `sideEffects` alanı verir ve üç ayarı vardır.

**Alan yoksa** araç temkinli davranır: hiçbir modülü tümüyle düşürmez, yalnız ölü dışa
aktarım bildirimlerini siler. Çıktı 3546 bayttır ve doğrudur, ama tahmin modülü içi
boşalmış hâlde çıktıda kalır.

**Alan `false` ise** yazar, paketteki hiçbir modülün en üst düzeyde iş yapmadığına söz
vermiş olur. Araç ölçek kaydını ve tahmin modülünü düşürür, kayıt modülündeki `kaydet`
adı da ölür. Çıktı 3231 bayta iner — ve yukarıdaki ikinci satırın davranışını üretir. Bu
paket için `false` bir yalandır.

**Alan bir liste ise** yazar hangi dosyaların yan etkili olduğunu tek tek söyler. Ölçek
kaydı korunur, tahmin modülü düşer. Çıktı 3408 bayttır: doğruluğu bozmadan elde edilebilen
en küçük çıktı.

Alan bu yüzden bir ayar değil, bir taahhüttür. Yanlış doldurulduğunda araç yanlış eleme
yapar ve arıza, elemenin yapıldığı yerde değil, kaybolan davranışın kullanıldığı yerde
görünür.

#### Elemeyi Durduran Öteki Yazımlar

Yan etki tek engel değildir. Aşağıdaki yazımların hepsi ad düzeyi izlemeyi kırar ve
aracı modülün tamamını tutmaya zorlar.

**Ad alanı nesnesinin bütün olarak dolaştırılması.** `import * as arac from "./arac.js"`
yazımından sonra nesne bir işleve verilirse, hangi adların okunacağı statik olarak
bilinemez. Adlar tek tek içe aktarıldığında bu belirsizlik doğmaz.

**Hesaplanmış erişim.** `arac[secilenAd]` biçiminde bir okuma, çalışma zamanında
belirlenen bir ada karşılık gelir. Böyle bir erişimi gören araç, o modüldeki bütün adları
canlı saymak zorundadır.

**Çağrı tabanlı modül biçimi.** CommonJS'te dışa aktarım nesnesi çalışma zamanında
değiştirilebilir; hangi adın var olduğu değerlendirme sonuna kadar kesin değildir. Statik
ayrıştırmayla elde edilen liste eksik olabileceği için eleme uygulanmaz.

**Değerlendirme anında iş yapan bildirimler.** En üst düzeydeki bir çağrının sonucu bir
sabite bağlanıyorsa, o çağrının yan etkisi olup olmadığı bilinmez. Araçlar bu belirsizliği
kaynağa konan saflık açıklamalarıyla aşar; açıklama da bildirim alanı gibi bir taahhüttür.

**Toplayıcı dosyalar.** Toplayıcının kendisi zararsızdır — örnekteki toplayıcının ölü
yeniden dışa aktarımları düştü. Zararlı olan, toplayıcının yan etkili bir modülü çıplak
içe aktarımla çizgeye sokmasıdır: o tek satır, toplayıcıyı içe aktaran her tüketiciye o
modülü taşır.

Bu liste, kütüphane yazarına dört maddelik somut bir sorumluluk bırakır: en üst düzeyde iş
yapmamak, adları doğrudan dışa aktarmak, yan etkili dosyaları bildirimde listelemek ve
toplayıcıya çıplak içe aktarım koymamak.

#### Özet

- Ağaç sarsma çizgeyi `modül#ad` düzeyine indirir; canlılık giriş noktasının dışa
  aktarımlarından başlayıp gövdelerde geçen adlar üzerinden yayılır.
- Yeniden dışa aktarımlar canlılığı ada göre iletir; dinamik içe aktarım hedefinin bütün
  dışa aktarımları temkinli olarak canlı sayılır.
- Dışa aktarımı olmayıp en üst düzeyde iş yapan bir modül ad düzeyi çözümlemesiyle
  elenemez; elenirse arıza sessizdir — örnekte Fahrenhayt sütunu hatasız biçimde kaybolur.
- Paket bildirimindeki yan etki alanı üç sonuç üretir: alan yoksa 3546 bayt ve modül
  düşmez, `false` ise 3231 bayt ve davranış bozulur, doğru liste ise 3408 bayt ve davranış
  korunur.
- Ad alanı nesnesinin dolaştırılması, hesaplanmış erişim, çağrı tabanlı modül biçimi ve
  değerlendirme anında iş yapan bildirimler elemeyi durdurur.

#### Sonraki Adım

Çizge şimdiye kadar yalnız betik dosyalarından kuruldu. Oysa bir arayüzün çıktısı yalnız
koddan oluşmaz: ölçüm listesinin biçem dosyası, istasyon simgesi, arayüzün yazı tipi ve
tarayıcıya olduğu gibi kopyalanacak birkaç statik dosya da çıktıya girer. Bunların bir
kısmı koddan içe aktarılır ve çizgenin düğümü hâline gelir; bir kısmı hiç içe aktarılmaz
ama yine de yayımlanır. Sonraki ders bu ikinci akışı ele alır: varlıkların boru hattından
geçirilmesi, küçük olanların gövdeye gömülmesi, büyük olanların ayrı dosya olarak
yazılması ve kaynaktaki adların çıktıdaki adlara bağlanması.

### Varlık İşleme

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/varlik-isleme

Görsel, yazı tipi ve statik dosyaların derleme çıktısına girmesi; bağların çizgeye eklenmesi, gömme eşiği kararı, referansların yeniden yazılması, varlık bildirimi ve doğrudan kopyalanan dizin.

Üç ders boyunca çizge yalnız betik dosyalarından kuruldu. Kuzey Yamaç Ölçüm İstasyonu
arayüzünün çıktısı bundan ibaret değildir: ölçüm listesinin biçem dosyası, listedeki her
satırın önündeki simge, istasyon yükselti eğrisinin çizimi, arayüzün yazı tipi ve arama
motorlarının okuduğu birkaç sabit adlı dosya da yayımlanır.

Bu dosyalar iki ayrı yoldan çıktıya girer. Bir kısmı koddan ya da biçem dosyasından
bağlanır; bağ, çizgede bir kenardır ve varlık bir düğüm hâline gelir. Bir kısmı hiçbir
yerden bağlanmaz ama yine de yayımlanmak zorundadır. Bu ders iki yolu ayırır ve bağlanan
varlıkların geçtiği boru hattını çalıştırır.

#### Varlık Bir Çizge Düğümüdür

Bir varlığın çizgeye girebilmesi için bağın **statik olarak okunabilir** olması gerekir.
İki standart yazım bunu sağlar.

Biçem dosyasındaki `url()` bildirimi, göreli bir yolu adres olarak verir. Kaynaktaki yol,
biçem dosyasının bulunduğu dizine göre çözülür.

Koddaki `new URL("./x.svg", import.meta.url)` yazımı aynı işi betik tarafında yapar. İkinci
argüman modülün kendi adresidir; ifade, modülün konumuna göre çözülmüş bir adres üretir. Bu
yazım standardın parçasıdır ve herhangi bir araca bağlı değildir; bir araç yokken de
çalışır, bir araç varken de derleme zamanında yakalanabilir.

Yakalanamayan yazım, adresin çalışma zamanında kurulmasıdır. `"/varlik/" + ad + ".svg"`
biçiminde bir birleştirme, derleme sırasında hangi dosyayı gösterdiğini söylemez. Böyle bir
bağ boru hattına girmez: dosya kopyalanmaz, adı yeniden yazılmaz ve çıktıda bulunmayan bir
adrese istek gider. Kural nettir — çalışma zamanında hesaplanan varlık adresleri, doğrudan
kopyalanan dizinden karşılanır.

#### İki Akış

**İşlenen akış**, bağlanan varlıkları kapsar. Bu varlıklar boyutlarına göre ya çıktıya ayrı
dosya olarak yazılır ya da bağlandıkları dosyanın gövdesine gömülür; her iki durumda da
kaynaktaki bağ, çıktıdaki adrese çevrilir.

**Doğrudan kopyalanan akış**, adı ve konumu değişmeden yayımlanması gereken dosyalar
içindir: gezgin kurallarını taşıyan dosya, alan adı doğrulama dosyaları, sabit bir adresten
indirilen veri dosyaları. Bu dosyalar taranmaz, adları korunur ve içerikleri değişmez.
Ayrımın ölçütü tek bir sorudur: adresi derlemenin belirlemesi serbest mi, yoksa dışarıdan
sabitlenmiş mi?

#### Varlık Ağacı

Aşağıdaki blok varlıkları belirlenimci biçimde üretir. Yazı tipi gövdesi yerine aynı
boyutta bir dolgu dosyası konur; boru hattının kararları dosyanın içeriğine değil boyutuna
bağlı olduğu için bu, sonucu değiştirmez.

Aşağıdaki blok, önceki derslerde kurulan `kaynak/` ağacına varlık bağlarını ekler; o derslerin kurulum blokları bu dizinde önce çalıştırılmalıdır.

```bash
#!/usr/bin/env bash
mkdir -p varlik genel bicem

cat > varlik/simge.svg <<'EOF'
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#243">
<path d="M2 13 L6 5 L9 10 L11 7 L14 13 Z"/><circle cx="12" cy="3" r="2"/>
</svg>
EOF

node --input-type=module -e '
import { writeFileSync } from "node:fs";
// Istasyon haritasi: 60 parcali bir yukselti egrisi, belirlenimci uretim.
const nokta = Array.from({ length: 60 }, (_, i) =>
  `${(i * 8).toFixed(0)} ${(120 - 40 * Math.sin(i / 6) - i / 3).toFixed(2)}`);
writeFileSync("varlik/istasyon.svg",
  `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 160">\n` +
  `<path fill="none" stroke="#1b3a2f" stroke-width="2" d="M${nokta.join(" L")}"/>\n` +
  nokta.filter((_, i) => i % 6 === 0).map((n) => `<circle cx="${n.split(" ")[0]}" cy="${n.split(" ")[1]}" r="3"/>`).join("\n") +
  `\n</svg>\n`);
// Yazi tipi govdesi yerine ayni boyutta belirlenimci bir ikili dosya konur.
writeFileSync("varlik/olcum.woff2", Buffer.alloc(4096, 0x77));
'

cat > bicem/istasyon.css <<'EOF'
@font-face {
  font-family: "Olcum";
  src: url("../varlik/olcum.woff2") format("woff2");
  font-display: swap;
}
.olcum li::before {
  content: "";
  background-image: url("../varlik/simge.svg");
  width: 16px;
  height: 16px;
}
.istasyon-harita {
  background-image: url("../varlik/istasyon.svg");
  min-height: 160px;
}
EOF

cat > kaynak/varlik-baglari.js <<'EOF'
export const simgeUrl = new URL("../varlik/simge.svg", import.meta.url);
export const haritaUrl = new URL("../varlik/istasyon.svg", import.meta.url);
EOF

printf 'User-agent: *\nDisallow: /panel/\n' > genel/robots.txt
printf '{ "istasyon": "kuzey-yamac", "yukseklik_m": 1840 }\n' > genel/istasyon.json
```

#### Boru Hattı

Boru hattı dört adımdır: bağları tara, her varlık için gömme kararı ver, referansı çıktı
adresiyle değiştir, kaynak ile çıktı arasındaki eşlemeyi bir bildirime yaz.

```js
// varlik.mjs — varlik boru hatti: tarama, gomme karari, yeniden yazma, bildirim uretimi.
import { readFileSync, writeFileSync, mkdirSync, cpSync, statSync, rmSync } from "node:fs";
import { basename, dirname, join, relative } from "node:path";

const GOMME_ESIGI = 1024;                        // bayt
const CIKTI = "cikti";
const TUR = { ".svg": ["image/svg+xml", "metin"], ".css": ["text/css", "metin"],
              ".woff2": ["font/woff2", "ikili"], ".png": ["image/png", "ikili"] };
const uzanti = (y) => y.slice(y.lastIndexOf("."));

const KAYNAKLAR = ["bicem/istasyon.css", "kaynak/varlik-baglari.js"];
const BAG = [
  /url\(\s*["']?([^"')]+)["']?\s*\)/g,                          // bicem dosyasindaki bag
  /new URL\(\s*["']([^"']+)["']\s*,\s*import\.meta\.url\s*\)/g, // koddaki bag
];

function veriUrl(yol) {                          // metin varlikta yuzde kodlama, ikilide base64
  const [mime, sinif] = TUR[uzanti(yol)], govde = readFileSync(yol);
  return sinif === "metin"
    ? "data:" + mime + "," + encodeURIComponent(govde.toString("utf8")).replace(/%20/g, " ")
    : "data:" + mime + ";base64," + govde.toString("base64");
}

rmSync(CIKTI, { recursive: true, force: true });
mkdirSync(join(CIKTI, "varlik"), { recursive: true });

const varliklar = new Map();                     // yol -> { boyut, referans, gomulu, hedef, ciktiBoyut }
const bildirim = {};

for (const kaynakYolu of KAYNAKLAR) {
  let metin = readFileSync(kaynakYolu, "utf8");
  for (const kalip of BAG) {
    metin = metin.replace(kalip, (tam, bag) => {
      const yol = relative(".", join(dirname(kaynakYolu), bag));
      if (!varliklar.has(yol)) {
        const boyut = statSync(yol).size;
        const gomulu = boyut <= GOMME_ESIGI;
        const hedef = gomulu ? veriUrl(yol) : "/varlik/" + basename(yol);
        if (!gomulu) cpSync(yol, join(CIKTI, hedef));
        varliklar.set(yol, { boyut, referans: 0, gomulu, hedef,
          ciktiBoyut: gomulu ? Buffer.byteLength(hedef) : boyut });
      }
      const v = varliklar.get(yol);
      v.referans++;
      return tam.startsWith("url") ? 'url("' + v.hedef + '")'
        : 'new URL("' + v.hedef + '", import.meta.url)';
    });
  }
  const ciktiYolu = join(CIKTI, basename(kaynakYolu));
  writeFileSync(ciktiYolu, metin);
  bildirim["/" + basename(kaynakYolu)] = { kaynak: kaynakYolu, boyut: statSync(ciktiYolu).size };
}

for (const [yol, v] of varliklar)
  if (!v.gomulu) bildirim[v.hedef] = { kaynak: yol, boyut: v.boyut };
cpSync("genel", CIKTI, { recursive: true });     // dogrudan kopyalanan dizin
writeFileSync(join(CIKTI, "varlik-bildirimi.json"), JSON.stringify(bildirim, null, 2) + "\n");

const b64 = (yol) => Math.ceil(statSync(yol).size / 3) * 4;
console.log("varlik".padEnd(20) + "boyut".padStart(6) + "  ref  karar       cikti  gomulseydi");
for (const [yol, v] of varliklar)
  console.log(basename(yol).padEnd(20) + String(v.boyut).padStart(6) + "   " + v.referans +
    "   " + (v.gomulu ? "gomuldu   " : "ayri dosya") + String(v.ciktiBoyut).padStart(7) +
    String(b64(yol)).padStart(12));

console.log("-- yeniden yazilmis bicem dosyasi (uzun degerler kisaltildi) --");
for (const s of readFileSync(join(CIKTI, "istasyon.css"), "utf8").trimEnd().split("\n"))
  console.log("  " + (s.length > 64 ? s.slice(0, 64) + "[...]" : s));

console.log("-- bildirim anahtarlari --");
for (const [url, k] of Object.entries(bildirim))
  console.log("  " + url.padEnd(24) + String(k.boyut).padStart(5) + " B  <- " + k.kaynak);

const ayri = [...varliklar.values()].filter((v) => !v.gomulu);
console.log("-- toplam --");
console.log("  bicem dosyasi   : " + bildirim["/istasyon.css"].boyut + " B  (kaynakta " +
  statSync("bicem/istasyon.css").size + " B)");
console.log("  ayri varlik     : " + ayri.length + " dosya, " +
  ayri.reduce((t, v) => t + v.boyut, 0) + " B");
console.log("  dogrudan kopya  : robots.txt, istasyon.json (ad ve icerik degismedi)");
```

```
$ node varlik.mjs
varlik               boyut  ref  karar       cikti  gomulseydi
olcum.woff2           4096   1   ayri dosya   4096        5464
simge.svg              168   2   gomuldu       275         224
istasyon.svg          1162   2   ayri dosya   1162        1552
-- yeniden yazilmis bicem dosyasi (uzun degerler kisaltildi) --
  @font-face {
    font-family: "Olcum";
    src: url("/varlik/olcum.woff2") format("woff2");
    font-display: swap;
  }
  .olcum li::before {
    content: "";
    background-image: url("data:image/svg+xml,%3Csvg xmlns%3D%22ht[...]
    width: 16px;
    height: 16px;
  }
  .istasyon-harita {
    background-image: url("/varlik/istasyon.svg");
    min-height: 160px;
  }
-- bildirim anahtarlari --
  /istasyon.css             575 B  <- bicem/istasyon.css
  /varlik-baglari.js        404 B  <- kaynak/varlik-baglari.js
  /varlik/olcum.woff2      4096 B  <- varlik/olcum.woff2
  /varlik/istasyon.svg     1162 B  <- varlik/istasyon.svg
-- toplam --
  bicem dosyasi   : 575 B  (kaynakta 323 B)
  ayri varlik     : 2 dosya, 5258 B
  dogrudan kopya  : robots.txt, istasyon.json (ad ve icerik degismedi)
```

Yeniden yazılmış biçem dosyasında üç bağın üçü de değişmiştir: ikisi kök göreli bir adrese,
biri gövdenin içine. Kaynaktaki `../varlik/` göreli yolu çıktıda hiç görünmez — çıktı
düzeni kaynak düzeninden bağımsızdır.

Referans sütunu bir ayrıntıyı açığa çıkarır: simge ve harita, hem biçem dosyasından hem
koddan bağlanmıştır. Ayrı dosya olarak yazılan harita bir kez kopyalanır ve iki referans
aynı adrese bakar. Gömülen simge ise her referansta yeniden yazılır; 168 baytlık dosya iki
çıktıda toplam 550 bayt tutar.

#### Gömme Eşiği

Gömme kararı bir eşikle verilir ve eşik iki karşıt maliyeti dengeler.

**Gömmenin kazancı bir istektir.** Ayrı dosya olarak yazılan her varlık, taşındığı baytın
yanında bir de istek gecikmesi getirir. Küçük varlıklarda bu gecikme, dosyanın kendisinden
pahalıdır.

**Gömmenin bedeli üç kalemdir.** Birincisi kodlama genişlemesidir: ikili veri base64'e
çevrildiğinde yaklaşık üçte bir büyür — 4096 baytlık yazı tipi gövdesi 5464 karaktere
çıkar. Metin varlıklarında yüzde kodlaması kullanılabilir, ama her zaman daha ucuz
değildir: örnekteki simge yüzde kodlamasıyla 275, base64 ile 224 bayttır, çünkü kaçırılması
gereken karakter oranı yüksektir. İkincisi çoğaltmadır: gömülen varlık, kendisini bağlayan
her çıktıda ayrı ayrı bulunur. Üçüncüsü önbelleklemedir — gömülen varlığın ayrı bir adresi
olmadığı için ayrı önbelleklenemez; bağlandığı dosya her değiştiğinde onunla birlikte
yeniden iner.

Bu üçüncü kalem, eşiğin neden küçük tutulduğunu açıklar. Nadiren değişen bir görselin, sık
değişen bir biçem dosyasının içine gömülmesi, o görseli her yayında yeniden indirtir.
Sonraki ders bu bağı ölçülebilir hâle getirecek.

#### Varlık Sınıflarına Özgü Kararlar

Eşik tek başına yeterli bir politika değildir; varlık sınıfının kendi kısıtları vardır.

**Yazı tipleri** boyutlarına bakılmaksızın ayrı dosya olarak yazılır. Bir yazı tipi
gövdesi birden çok sayfada ortaktır ve uzun ömürle önbelleklenir; gömülmesi bu paylaşımı
yok eder. CSS ile Görsel Sunum kursundaki yazı tipi yükleme dersinde kurulan iki ayar
burada da geçerlidir: görüntüleme davranışı `font-display` ile bildirilir ve gövde, belge
içinde erken bir yükleme ipucuyla istenebilir. Derleme tarafının ekleyebileceği üçüncü şey
alt küme çıkarmadır — gövdeden yalnız kullanılan karakter aralıklarının bırakılması,
dosyayı belirgin biçimde küçültür ve karar, hedeflenen dil kümesine bağlıdır.

**Görseller** iki ek üstveri taşır. Birincisi ölçülerdir: genişlik ve yükseklik derleme
sırasında okunup işaretlemeye yazıldığında, Web'in Temelleri kursunda tanımlanan içerik
kayması önlenir. İkincisi türevlerdir: Düzen Sistemleri ve Duyarlı Tasarım kursundaki aday
kümesi, aynı görselin birkaç genişlikte üretilmiş sürümlerini ister; bu sürümleri üreten
yer boru hattıdır. Biçim seçimi ürün adıyla değil sınıfla yapılır — kayıpsız sıkıştırma
isteyen çizimler ile kayıplı sıkıştırmaya uygun fotoğraflar ayrı ele alınır.

**Biçem dosyaları** hem varlık hem kaynaktır: içlerindeki bağlar taranır, kendileri de
çıktıya yazılır. Bu yüzden boru hattında iki kez görünürler.

#### Varlık Bildirimi

Boru hattının son çıktısı bir eşlemedir: hangi kaynak dosyanın çıktıda hangi adrese
karşılık geldiği. Bu dosyaya **varlık bildirimi (asset manifest)** denir ve derleme ile
onun dışındaki her şey arasındaki sözleşmedir.

Bildirimi okuyan en az üç taraf vardır. Belge şablonu, biçem dosyasının ve giriş parçasının
adresini oradan alır. Sunucuda oluşturan bir çalışma zamanı, ürettiği işaretlemeye aynı
adresleri yazar. Çevrimdışı çalışan bir servis çalışanı, önceden önbelleğe alacağı dosya
listesini oradan üretir — Tarayıcı ve Web Platformu kursundaki önbellek stratejileri dersi
bu listeyi elle yazmanın kırılganlığını göstermişti; bildirim, listeyi derlemenin
üretmesini sağlar.

Ad benzerliği üç ayrı dosyayı karıştırmaya elverişlidir. Paket bildirimi bağımlılıkları ve
paket üstverisini taşır; uygulama bildirimi kurulabilirlik bilgisini taşır; varlık
bildirimi ise yalnız kaynak–çıktı eşlemesidir. Üçü farklı zamanlarda, farklı taraflarca
okunur.

#### Özet

- Varlıklar çizgeye iki standart bağ üzerinden girer: biçem dosyasındaki `url()` bildirimi
  ve koddaki `new URL(...)` ifadesi; çalışma zamanında birleştirilen adresler boru hattına
  giremez.
- Bağlanan varlıklar işlenen akıştan, adı sabitlenmiş dosyalar doğrudan kopyalanan akıştan
  geçer; ölçüt, adresi derlemenin belirleyip belirleyemeyeceğidir.
- Boru hattı dört adımdır: bağları tara, gömme kararı ver, referansı çıktı adresiyle
  değiştir, eşlemeyi bildirime yaz.
- Gömme bir istek kazandırır; karşılığında kodlama genişlemesi, her referansta çoğaltma ve
  ayrı önbelleklenememe getirir — 4096 baytlık gövde base64'te 5464 karaktere çıkar.
- Yazı tipleri boyutundan bağımsız ayrı dosya olarak yazılır; görseller ölçü üstverisi ve
  genişlik türevleriyle birlikte üretilir.
- Varlık bildirimi, belge şablonu, sunucu çalışma zamanı ve servis çalışanı için ortak
  sözleşmedir.

#### Sonraki Adım

Çıktıdaki dosyalar şimdilik kaynaktaki adlarını taşıyor. Bu, iki sorunun ikisini de
çözümsüz bırakır. Yayımlanan bir dosya uzun ömürle önbelleklenirse, içeriği değiştiğinde
kullanıcıya eskisi verilmeye devam eder; kısa ömürle önbelleklenirse her ziyarette yeniden
doğrulama yapılır ve önbelleğin değeri kaybolur. İki uçtan da kaçınmanın yolu adı içeriğe
bağlamaktır: dosyanın adı içeriğinin özetini taşıdığında, içerik değişmedikçe ad değişmez
ve ad değişmedikçe önbellek geçerli kalır. Sonraki ders bu adları üretir, adların
birbirine bağlı parçalarda nasıl zincirleme değiştiğini ölçer ve zinciri kıran düzeneği
kurar.

### Önbellek Kırma

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/onbellek-kirma

Dosya adının içeriğe bağlanması; karma üretimi ve belirlenimcilik, karmanın bağımlı parçalarda zincirleme değişmesi, içe aktarım haritasıyla zincirin kırılması ve ada göre önbellek politikası.

Bir önceki dersin çıktı dizinindeki dosyalar kaynaktaki adlarını taşıyordu. Bu, önbellek
tarafında çözümsüz bir ikilem bırakır. Kuzey Yamaç Ölçüm İstasyonu sitesinin biçem dosyası
`/istasyon.css` adresinden veriliyorsa ve bu adrese uzun ömürlü bir önbellek politikası
uygulanıyorsa, dosyanın içeriği değiştiğinde kullanıcıya eskisi verilmeye devam eder.
Politika kısa tutulursa her ziyarette yeniden doğrulama yapılır ve önbelleğin kazancı
yeniden doğrulama turlarına gider.

İkilemin kaynağı adresin içerikten bağımsız olmasıdır. Adres içeriğe bağlanırsa ikilem
ortadan kalkar: içerik değişmedikçe adres değişmez ve o adres sonsuza yakın bir süre
önbellekte tutulabilir; içerik değişirse adres de değişir ve yeni adres önbellekte hiç
bulunmadığı için kaçınılmaz olarak indirilir. **Önbellek kırma (cache busting)**, bu bağı
kuran adlandırma yöntemidir.

#### Adı İçeriğe Bağlamak

Bağ bir özet işleviyle kurulur. Dosyanın baytları bir kriptografik özet işlevinden
geçirilir, sonucun bir bölümü ada eklenir. Sonuç iki özelliği birden sağlar.

**Belirlenimcilik:** aynı baytlar aynı adı verir. Aynı kaynaktan iki kez derleme yapıldığında
adlar özdeş çıkar; bu, derlemenin yeniden üretilebilir olması için gereken koşullardan
biridir ve önizleme dağıtımları dersinde tekrar kullanılacaktır.

**Çakışmama:** farklı baytların aynı adı vermesi olasılığı, alınan özet uzunluğuyla
denetlenir. Sekiz onaltılık basamak, dört milyara yakın ayrı değer verir; bir yayındaki
dosya sayısı bunun yanında küçük kaldığı için pratikte yeterlidir. Kısaltma bir güvenlik
kararı değil, ad uzunluğu kararıdır — bütünlük denetimi ayrı bir düzenekle, alt kaynak
bütünlüğüyle yapılır.

Karmanın **son içerik üzerinden** hesaplanması zorunludur. Dönüşümlerden önce hesaplanan
bir karma, çıktıdaki baytları temsil etmez; ad, içeriğin değiştiği bazı durumlarda aynı
kalır ve önbellek eski dosyayı vermeye devam eder.

#### Zincirleme Değişim

Karmalı adlandırmanın kolay görünen tarafı budur. Zor tarafı, çıktıların birbirine
bağlı olmasıdır.

Bir parça, dinamik olarak yüklediği başka bir parçanın adını gövdesinde taşır. O parçanın
adı değiştiğinde, bu parçanın **içeriği** değişir; içeriği değişince karması değişir;
karması değişince adı değişir. Değişim çizgede yapraktan köke doğru yayılır. Üç katmanlı
bir zincirde bir yaprak modülün tek satırlık değişikliği üç dosyanın adını değiştirir ve
geri dönen bir ziyaretçiye üçünü birden yeniden indirtir.

Zinciri kıran şey, adın gövdeye gömülmemesidir. Modül belirteçleri bir **içe aktarım
haritası (import map)** üzerinden çözülürse, parçanın gövdesi çocuğunun karmalı adını
taşımaz; yalnız değişmeyen bir belirteç taşır. Harita, belgeye yazılan ve önbelleklenmeyen
küçük bir eşlemedir.

#### Yayın Üretimi

Aşağıdaki betik iki adlandırma kipini yan yana çalıştırır ve iki değişiklik senaryosunun
yeniden indirttiği baytı sayar. Biçem dosyası ve iki varlık bir önceki dersin çıktı
dizininden okunur.

Bu dersin betiği, Varlık İşleme dersinin ürettiği `cikti/` ağacını okur; o dersin blokları bu dizinde önce çalıştırılmalıdır.

```js
// karma.mjs — icerik karmali dosya adlari, zincirleme degisim ve ice aktarim haritasi.
import { createHash } from "node:crypto";
import { readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
import { dirname, join } from "node:path";

const karma = (govde) => createHash("sha256").update(govde).digest("hex").slice(0, 8);
const karmali = (ad, k) => ad.replace(/\.([^.]+)$/, "-" + k + ".$1");

// Cikti kumesi. Govdesi null olanlar dorduncu dersin cikti dizininden okunur.
// {{...}} yer tutucusu, cocuk ciktinin yayindaki adiyla degistirilir.
const GOVDE = {
  "varlik/istasyon.svg": null,
  "varlik/olcum.woff2": null,
  "istasyon.css": '@font-face { font-family: "Olcum";\n' +
    '  src: url("{{varlik/olcum.woff2}}") format("woff2"); font-display: swap; }\n' +
    '.istasyon-harita { background-image: url("{{varlik/istasyon.svg}}"); }\n',
  "parca/grafik.js": 'export const yol = (o) => o.map((x) => x.sicaklik).join(" ");\n',
  "parca/panel.js": 'import { yol } from "{{parca/grafik.js}}";\n' +
    'export const ciz = (o) => "<svg>" + yol(o) + "</svg>";\n',
  "giris.js": 'export const ac = async () => (await import("{{parca/panel.js}}")).ciz;\n',
};
const SIRA = ["varlik/istasyon.svg", "varlik/olcum.woff2", "istasyon.css",
              "parca/grafik.js", "parca/panel.js", "giris.js"];
const BELIRTEC = { "parca/grafik.js": "kuzey:grafik", "parca/panel.js": "kuzey:panel" };

function yayinla(kip, degisiklik = {}) {
  const ad = {}, govde = {}, boyut = {};
  for (const yol of SIRA) {
    let g = degisiklik[yol] ?? GOVDE[yol] ?? readFileSync(join("cikti", yol));
    if (typeof g === "string") {
      // gomulu kip: cocugun karmali adi govdeye yazilir.
      // harita kipi: yalniz modul belirtecleri ciplak birakilir; bicem dosyasindaki
      // url() bagi ice aktarim haritasiyla cozulemez, adi gommeye devam eder.
      g = Buffer.from(g.replace(/\{\{([^}]+)\}\}/g, (_, cocuk) =>
        kip === "harita" && BELIRTEC[cocuk] ? BELIRTEC[cocuk] : "/" + ad[cocuk]));
    }
    ad[yol] = karmali(yol, karma(g));
    govde[yol] = g;
    boyut[yol] = g.length;
  }
  const harita = { imports: Object.fromEntries(
    Object.entries(BELIRTEC).map(([yol, belirtec]) => [belirtec, "/" + ad[yol]])) };
  return { ad, govde, boyut, harita: Buffer.from(JSON.stringify(harita) + "\n") };
}

const ilk = yayinla("gomulu");
console.log("-- birinci yayin --");
for (const yol of SIRA) console.log("  " + yol.padEnd(22) + ilk.ad[yol]);
console.log("  ayni kaynak iki kez derlendi, adlar ozdes: " +
  SIRA.every((y) => ilk.ad[y] === yayinla("gomulu").ad[y]));

rmSync("yayin", { recursive: true, force: true });
for (const yol of SIRA) {
  const hedef = join("yayin", ilk.ad[yol]);
  mkdirSync(dirname(hedef), { recursive: true });
  writeFileSync(hedef, ilk.govde[yol]);
}

const SENARYO = {
  "yaprak modul degisti": {
    "parca/grafik.js": GOVDE["parca/grafik.js"].replace('join(" ")', 'join(", ")'),
  },
  "gorsel varlik degisti": {
    "varlik/istasyon.svg": Buffer.from(readFileSync("cikti/varlik/istasyon.svg", "utf8")
      .replace('stroke-width="2"', 'stroke-width="3"')),
  },
};

for (const [baslik, degisiklik] of Object.entries(SENARYO)) {
  console.log("-- " + baslik + " --");
  for (const kip of ["gomulu", "harita"]) {
    const onceki = yayinla(kip), sonraki = yayinla(kip, degisiklik);
    const degisen = SIRA.filter((y) => onceki.ad[y] !== sonraki.ad[y]);
    const haritaDegisti = !onceki.harita.equals(sonraki.harita);
    const inen = degisen.reduce((t, y) => t + sonraki.boyut[y], 0) +
      (haritaDegisti ? sonraki.harita.length : 0);
    console.log("  " + kip.padEnd(8) + " adi degisen: " + degisen.join(", "));
    console.log("           yeniden inen: " + inen + " B / toplam " +
      SIRA.reduce((t, y) => t + sonraki.boyut[y], 0) + " B" +
      (haritaDegisti ? "  (harita da yenilendi)" : ""));
  }
}

console.log("-- harita kipinde uretilenler --");
process.stdout.write("  giris.js  : " + yayinla("harita").govde["giris.js"]);
process.stdout.write("  panel.js  : " + yayinla("harita").govde["parca/panel.js"]
  .toString().split("\n")[0] + "\n");
process.stdout.write("  harita    : " + yayinla("harita").harita);
```

```
$ node karma.mjs
-- birinci yayin --
  varlik/istasyon.svg   varlik/istasyon-77c2bd82.svg
  varlik/olcum.woff2    varlik/olcum-7b962f03.woff2
  istasyon.css          istasyon-7e1d6934.css
  parca/grafik.js       parca/grafik-69c71ea6.js
  parca/panel.js        parca/panel-c3924741.js
  giris.js              giris-5f45daac.js
  ayni kaynak iki kez derlendi, adlar ozdes: true
-- yaprak modul degisti --
  gomulu   adi degisen: parca/grafik.js, parca/panel.js, giris.js
           yeniden inen: 343 B / toplam 5697 B  (harita da yenilendi)
  harita   adi degisen: parca/grafik.js
           yeniden inen: 161 B / toplam 5671 B  (harita da yenilendi)
-- gorsel varlik degisti --
  gomulu   adi degisen: varlik/istasyon.svg, istasyon.css
           yeniden inen: 1356 B / toplam 5696 B
  harita   adi degisen: varlik/istasyon.svg, istasyon.css
           yeniden inen: 1356 B / toplam 5670 B
```

```
-- harita kipinde uretilenler --
  giris.js  : export const ac = async () => (await import("kuzey:panel")).ciz;
  panel.js  : import { yol } from "kuzey:grafik";
  harita    : {"imports":{"kuzey:grafik":"/parca/grafik-69c71ea6.js","kuzey:panel":"/parca/panel-7688ac67.js"}}
```

#### Çıktının Söyledikleri

Birinci bölümdeki doğrulama satırı, adlandırmanın belirlenimci olduğunu gösterir: aynı
kaynaktan iki kez üretilen adlar özdeştir. Bu, yayının bir kimliği hâline gelir — iki
derlemenin aynı sonucu verip vermediği, çıktıyı açmadan ad listesi karşılaştırılarak
anlaşılabilir.

İlk senaryo zincirlemenin bedelini verir. Gömülü adlandırmada, yaprak parçadaki tek
satırlık değişiklik üç dosyanın adını değiştirir ve geri dönen ziyaretçiye 343 bayt
indirtir. İçe aktarım haritası kipinde yalnız değişen dosyanın adı değişir ve yeniden inen
miktar 161 bayta düşer — yarıdan az. Oran, zincirin derinliğiyle büyür: dört katmanlı bir
parça çizgesinde gömülü adlandırma dört dosyayı, harita kipi yine bir dosyayı değiştirir.

İkinci senaryo yöntemin sınırını gösterir. Görsel bir varlığın değişmesi her iki kipte de
biçem dosyasının adını değiştirir, çünkü biçem dosyasındaki `url()` bağı içe aktarım
haritasıyla çözülemez; adres oraya yazılmak zorundadır. Zincir kırma yalnız modül
belirteçleri için geçerlidir. Bu, dördüncü dersteki gömme kararına yeni bir ölçüt ekler:
sık değişen bir varlığı biçem dosyasına gömmek, biçem dosyasını da her yayında değiştirir.

#### Ada Göre Önbellek Politikası

Karmalı adlandırma tek başına bir kazanç sağlamaz; kazancı önbellek başlıkları üretir.
Tarayıcı ve Web Platformu kursundaki önbellek stratejileri dersinde tanımlanan başlıklar
burada dosya sınıflarına göre uygulanır.

**Karmalı dosyalar** en uzun ömürle ve değiştirilemezlik bildirimiyle verilir: bir yıllık tazelik
süresi ve değiştirilemez işareti. Bu bildirimin doğru olmasının güvencesi adın kendisidir — içerik
değişirse dosya artık o adla istenmez. Değişmez işareti, tazelik süresi dolmadan yapılan yeniden
doğrulama isteklerini de kaldırır.

**Belge** hiçbir zaman uzun ömürle verilmez. Karmalı adları belge taşır; belge
önbelleklenirse kullanıcı eski adları istemeye devam eder ve yeni yayını hiç görmez. Belge
için doğru politika, her kullanımda yeniden doğrulanacak biçimde işaretlemek ve bir
doğrulayıcı vermektir; içerik değişmemişse yanıt gövdesiz döner.

**İçe aktarım haritası** belgenin içinde yer alır ve onunla aynı politikaya tabidir. Ayrı
bir dosyada tutulursa belge gibi işaretlenmelidir; uzun ömürle verilirse zinciri kırmak
için yapılan her şey boşa gider.

**Doğrudan kopyalanan dosyalar** adlarını koruduğu için karmalı politikayı alamaz. Bunlara
orta uzunlukta bir tazelik süresi ve doğrulayıcı verilir.

Sorgu dizgisiyle sürümleme — adres sonuna bir sürüm parametresi eklemek — aynı amaca hizmet
eder gibi görünür ama iki zayıflığı vardır: bazı ara önbellekler sorgu dizgisi taşıyan
adresleri önbelleklemez ve aynı dosyanın iki farklı parametreyle iki kopyası oluşabilir.
Ad üzerinden sürümleme bu iki sorunu da doğurmaz.

#### Eski Adların Ömrü

Karmalı adlandırmanın getirdiği bir yükümlülük vardır: **eski adlar bir süre daha
yaşamalıdır.** Bir kullanıcı sayfayı açıkken yeni bir yayın yapılırsa, açık sayfanın
belgesi eski adları taşımaya devam eder. O sayfadan tetiklenen bir dinamik parça isteği,
eski ada gider. Yeni yayın eski dosyaları sildiyse istek bulunamaz ve kod bölme dersinde
tanımlanan çalışma zamanı hatası ortaya çıkar.

Bu yüzden yayın, dosyaları yerinde değiştirmek yerine **eklemeli** yapılır: yeni adlar
yazılır, eski adlar silinmez. Silme, açık oturumların ömrünü aşan bir gecikmeyle ayrı bir
temizlik işi olarak yapılır. Aynı kural geri alma için de gerekir ve dağıtım konusundaki
aşamalı yayın dersinde bir yayın politikasına dönüşecektir.

#### Özet

- İçerik karması dosya adını içeriğe bağlar: aynı baytlar aynı adı verir, değişen baytlar
  yeni bir ad üretir; karma son içerik üzerinden hesaplanmalıdır.
- Adlandırma belirlenimcidir; aynı kaynaktan iki derleme özdeş ad listesi üretir ve bu
  liste yayının kimliği olarak kullanılabilir.
- Karmalı ad gövdeye gömüldüğünde değişim yapraktan köke zincirlenir: örnekte tek satırlık
  bir değişiklik üç dosyanın adını değiştirir ve 343 bayt yeniden indirtir.
- İçe aktarım haritası modül belirteçlerini gövdeden çıkarır ve zinciri kırar; aynı
  değişiklik 161 bayta iner. Biçem dosyasındaki varlık bağları bu yolla çözülemez.
- Politika dosya sınıfına göre verilir: karmalı dosyalar uzun ömürlü ve değiştirilemez, belge ve
  içe aktarım haritası her kullanımda yeniden doğrulanan.
- Eski adlar hemen silinmez; açık sayfaların isteyeceği parçalar bir süre daha yayında
  kalır.

#### Sonraki Adım

Buraya kadar üretilen her şey yayın içindir: parçalar birleştirilmiş, ölü kod atılmış,
varlıklar işlenmiş, adlar içeriğe bağlanmıştır. Bu işlemlerin hepsi zaman alır ve geliştirme
sırasında hiçbirinin karşılığı yoktur. Bir satırı değiştirip sonucu görmek isteyen biri için
tüm çizgenin yeniden paketlenmesi, karmaların yeniden hesaplanması ve sayfanın baştan
yüklenmesi gereksiz bir bekleme ve kaybolmuş bir ekran durumu demektir. Sonraki ders
derlemenin ikinci kipini kurar: kaynağı olduğu gibi sunan, yalnız değişen modülü yeniden
değerlendiren ve değişimi çizgede yukarı doğru yayan bir geliştirme sunucusu.

### Geliştirme Sunucusu

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/gelistirme-sunucusu

Derlemenin ikinci kipi; modüllerin istendikçe dönüştürülüp sunulması, doğrulayıcıyla yeniden kullanım, sıcak modül değişiminin geçersiz kılma yayılımı olarak modellenmesi ve kabul sınırının doğruluk koşulları.

Önceki beş ders tek bir kipi kurdu: yayın için derleme. Bu kipte çizge tümüyle taranır,
parçalar birleştirilir, ölü kod atılır, varlıklar işlenir ve adlar içeriğe bağlanır.
Adımların hepsinin karşılığı vardır — ama karşılığı yalnız yayında vardır.

Kuzey Yamaç Ölçüm İstasyonu arayüzünde bir biçimlendirme satırını değiştiren biri için bu
adımların hiçbiri işe yaramaz. Çizgenin tümünü yeniden taramak, karmaları yeniden
hesaplamak ve sayfayı baştan yüklemek iki şey kaybettirir: saniyeler ve ekrandaki durum.
Panelin açık olması, süzgecin ayarlanmış olması, kaydırma konumu — tam yükleme hepsini
siler.

Bu ders derlemenin ikinci kipini kurar. Sorusu şudur: bir dosya değiştiğinde en az ne
yapılabilir?

#### Kipler Arasındaki Fark

Geliştirme kipi, yayın kipinin adımlarının çoğunu düşürür.

**Paketleme düşer.** Tarayıcı, standart modülleri kendisi yükleyebilir; çizgeyi dolaşma işi
tarayıcının modül yükleyicisine bırakılır. Sunucunun görevi tek bir modülü istendiğinde
vermeye iner.

**Ağaç sarsma, küçültme ve içerik karması düşer.** Üçü de çıktı boyutunu küçültür; yerel
bağlantıda boyut belirleyici değildir ve üçü de tüm çizgeyi görmeyi gerektirir.

**Kalması gerekenler** ise üçtür: belirteç çözümü — tarayıcı çıplak belirteçleri
çözemez —, hedefin anlamadığı sözdizimin çevrilmesi ve kaynak haritası üretimi. Modüller,
Araçlar ve Ekosistem kursundaki kaynak haritaları dersi bu üçüncüsünün neden vazgeçilmez
olduğunu göstermişti: dönüştürülmüş kodda hata ayıklanamaz.

Bu düşürme, maliyeti çizge boyutundan bağımsız kılar. Yayın kipinde bir değişiklik tüm
çizgeyi ilgilendirir; geliştirme kipinde yalnız değişen dosyayı.

#### İstendikçe Sunma

Aşağıdaki sunucu tek bir dönüşüm yapar ve tek bir dosya sunar.

Bu dersin sunucusu, önceki derslerde kurulan `kaynak/` ağacını sunar; o derslerin kurulum blokları bu dizinde önce çalıştırılmalıdır.

```js
// gelistirme-sunucusu.mjs — modulleri istendikce, donusturerek ve dogrulayiciyla sunar.
import { createServer } from "node:http";
import { createHash } from "node:crypto";
import { readFileSync, existsSync } from "node:fs";

// Tek donusum: goreli belirtecler sunucudaki mutlak yollara cevrilir.
// Paketleme yok; her modul kendi dosyasi olarak kalir.
function donustur(yol, kaynak) {
  return "// [gelistirme] " + yol + "\n" + kaynak
    .replace(/from\s+"\.\/([^"]+)"/g, 'from "/kaynak/$1"')
    .replace(/^import\s+"\.\/([^"]+)"/gm, 'import "/kaynak/$1"')
    .replace(/import\(\s*"\.\/([^"]+)"\s*\)/g, 'import("/kaynak/$1")');
}

createServer((istek, yanit) => {
  yanit.sendDate = false;                        // ciktiyi belirlenimci kilmak icin
  const yol = istek.url.split("?")[0];
  const dosya = yol.replace(/^\//, "");
  if (!yol.startsWith("/kaynak/") || !existsSync(dosya)) {
    yanit.writeHead(404).end();
    return;
  }
  const govde = donustur(yol, readFileSync(dosya, "utf8"));
  const etiket = '"' + createHash("sha256").update(govde).digest("hex").slice(0, 12) + '"';
  if (istek.headers["if-none-match"] === etiket) {
    yanit.writeHead(304, { ETag: etiket }).end();
    return;
  }
  yanit.writeHead(200, {
    "Content-Type": "text/javascript; charset=utf-8",
    "Content-Length": Buffer.byteLength(govde),
    "Cache-Control": "no-cache",
    ETag: etiket,
  }).end(govde);
}).listen(8172, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8172"));
```

Çizgeyi dolaşan taraf tarayıcıdır. Aşağıdaki istemci, tarayıcının modül yükleme döngüsünün
küçük bir karşılığıdır: bir modülü alır, içindeki statik belirteçleri okur, onları ister.
Dinamik içe aktarımı izlemez — çünkü tarayıcı da izlemez, çağrı yürütülene kadar bekler.

```js
// istemci.mjs — tarayicinin modul yukleme dongusunun kucuk bir karsiligi.
// Yalniz statik belirtecler izlenir; import() cagrisi yurutulene kadar istenmez.
const KOK = "http://127.0.0.1:8172";
const STATIK = [/from\s*"([^"]+)"/g, /^import\s*"([^"]+)"/gm];

async function yukle(kokler) {
  const alinan = new Map(), kuyruk = [...kokler];
  while (kuyruk.length > 0) {
    const yol = kuyruk.shift();
    if (alinan.has(yol)) continue;
    const govde = await (await fetch(KOK + yol)).text();
    alinan.set(yol, govde.length);
    for (const kalip of STATIK)
      for (const [, b] of govde.matchAll(kalip)) if (b.startsWith("/kaynak/")) kuyruk.push(b);
  }
  return alinan;
}

const kisa = (k) => [...k].map(([y]) => y.replace("/kaynak/", "")).sort().join(", ");
const ilk = await yukle(["/kaynak/giris-tarayici.js"]);
console.log("ilk yuklemede alinan : " + ilk.size + " modul  " + kisa(ilk));
const sonra = await yukle([...ilk.keys(), "/kaynak/panel.js"]);
const ek = new Map([...sonra].filter(([y]) => !ilk.has(y)));
console.log("panel acilinca ek    : " + ek.size + " modul  " + kisa(ek));
console.log("uyari.js henuz istenmedi: " + !sonra.has("/kaynak/uyari.js"));
```

Uzun ömürlü sunucu kendi betiğiyle başlatılır. Bağlantı noktası olarak seçilen 8172
keyfîdir ve boş olmalıdır.

```bash
#!/usr/bin/env bash
# Sunucuyu baslatir, tek modulu ister, dogrulayiciyi sinar, modul dongusunu kosturur.
cp kaynak/sablon.js sablon.ilk
node gelistirme-sunucusu.mjs > /dev/null &
sunucu=$!
sleep 1

echo "--- tek modulun sunulan hali ---"
curl -sS http://127.0.0.1:8172/kaynak/sablon.js

ETIKET=$(curl -sS -o /dev/null -D - http://127.0.0.1:8172/kaynak/sablon.js \
  | awk 'tolower($1) == "etag:" { print $2 }' | tr -d '\r')
echo "--- dogrulayici $ETIKET ---"
curl -sS -o /dev/null -w "degismeden ikinci istek : %{http_code}\n" \
  -H "If-None-Match: $ETIKET" http://127.0.0.1:8172/kaynak/sablon.js
printf '\nexport const dipnot = () => "olcumler UTC";\n' >> kaynak/sablon.js
curl -sS -o /dev/null -w "dosya degistikten sonra : %{http_code}\n" \
  -H "If-None-Match: $ETIKET" http://127.0.0.1:8172/kaynak/sablon.js

echo "--- istendikce yukleme ---"
node istemci.mjs

kill "$sunucu"
mv sablon.ilk kaynak/sablon.js
```

```
--- tek modulun sunulan hali ---
// [gelistirme] /kaynak/sablon.js
import { olcumSatiri } from "/kaynak/bicim.js";

export const liste = (olcumler) =>
  "<ul>" + olcumler.map((o) => "<li>" + olcumSatiri(o) + "</li>").join("") + "</ul>";
export const baslik = (istasyon) => "<h1>" + istasyon + "</h1>";

--- dogrulayici "4e20f52c3876" ---
degismeden ikinci istek : 304
dosya degistikten sonra : 200
--- istendikce yukleme ---
ilk yuklemede alinan : 9 modul  arac.js, bicim.js, birim.js, giris-tarayici.js, kayit.js, olcek-kaydi.js, sablon.js, tahmin.js, tarih.js
panel acilinca ek    : 3 modul  disa-aktarma.js, grafik.js, panel.js
uyari.js henuz istenmedi: true
```

Doğrulayıcı değeri sunulan modülün baytlarından üretilir: aynı bayt dizisi her zaman aynı
değeri, tek karakterlik bir değişiklik başka bir değeri verir. Buradaki değer bu dizinin o
andaki içeriğine bağlıdır — önceki derslerin kurulum blokları aynı dosyayı yeniden yazdığı
için, hangi blokların çalıştırıldığına göre farklı bir doğrulayıcı görülebilir. Anlamlı olan
değerin kendisi değil, değişmeyen modülde **aynı** kalmasıdır.

Çıktının son üç satırı geliştirme kipinin kazancını gösterir. İlk yüklemede on üç modülün
dokuzu istenir; panel açılınca üçü daha gelir; eşik uyarısı modülü hiç istenmez, çünkü onu
isteyen dinamik çağrı henüz yürümemiştir. Hiçbir aşamada tüm çizge taranmaz.

Doğrulayıcı satırları ikinci kazancı verir. İçeriği değişmemiş bir modül, ikinci istekte
gövdesiz yanıtla geçiştirilir; sayfa yenilendiğinde yalnız değişen dosyalar yeniden iner.

#### Geçersiz Kılma Yayılımı

Sayfayı yenilemeden güncellemek bundan fazlasını gerektirir. Bir modülün yeni sürümü
alındığında, onu içe aktarmış modüllerin elindeki değerler eskidir: içe aktarılan bağlar
yeniden okunmalı, o modüller de yeniden değerlendirilmelidir. Değişim, çizgede kenar
yönünün tersine — istenenden isteyene doğru — yayılır.

Yayılımın sonsuza kadar sürmemesi için bir durak gerekir. **Kabul sınırı (accept
boundary)**, kendi güncellemesini karşılayabildiğini bildiren modüldür: yeni sürümüyle
değiştirilir, isteyenlerine haber verilmez. Sınır bulunamaz ve yayılım giriş noktasına
ulaşırsa yapılacak tek şey tam yüklemedir.

```js
// sicak.mjs — modul cizgesinde gecersiz kilma yayilimi ve kabul siniri.
import { readFileSync } from "node:fs";
import { dirname, join, relative } from "node:path";

const GIRIS = "kaynak/giris-tarayici.js";
const BAG = [/from\s+"(\.[^"]+)"/g, /^import\s+"(\.[^"]+)"/gm, /import\(\s*"(\.[^"]+)"\s*\)/g];

// Kabul siniri: guncellemesini kendisi karsilayan, importerlarina yaymayan modul.
const KABUL = new Set(["kaynak/sablon.js", "kaynak/panel.js", "kaynak/grafik.js"]);
// Modul duzeyinde durum tutan moduller: yeniden degerlendirilirse durum sifirlanir.
const DURUM = new Set(["kaynak/kayit.js"]);

const cizge = new Map(), kuyruk = [GIRIS];
while (kuyruk.length > 0) {
  const yol = kuyruk.shift();
  if (cizge.has(yol)) continue;
  const kaynak = readFileSync(yol, "utf8");
  const komsu = BAG.flatMap((k) => [...kaynak.matchAll(k)]
    .map(([, b]) => relative(".", join(dirname(yol), b))));
  cizge.set(yol, komsu);
  kuyruk.push(...komsu);
}

const isteyen = new Map([...cizge.keys()].map((y) => [y, []]));
for (const [yol, komsu] of cizge) for (const k of komsu) isteyen.get(k).push(yol);

function yayil(degisen) {
  const gecersiz = [], gorulen = new Set(), kuyruk = [degisen];
  let tamYukleme = false;
  while (kuyruk.length > 0) {
    const yol = kuyruk.shift();
    if (gorulen.has(yol)) continue;
    gorulen.add(yol);
    gecersiz.push(yol);
    if (KABUL.has(yol)) continue;                 // sinir bulundu, yukari yayilmaz
    if (yol === GIRIS) { tamYukleme = true; continue; }
    kuyruk.push(...isteyen.get(yol));
  }
  return { gecersiz, tamYukleme, durum: gecersiz.filter((y) => DURUM.has(y)) };
}

const ad = (y) => y.replace("kaynak/", "");
console.log("cizge: " + cizge.size + " modul, kabul siniri: " + [...KABUL].map(ad).join(", "));
console.log("degisen".padEnd(18) + "sonuc".padEnd(14) + "yeniden degerlendirilen kume");
for (const degisen of ["kaynak/grafik.js", "kaynak/panel.js", "kaynak/bicim.js",
                       "kaynak/birim.js", "kaynak/kayit.js", "kaynak/giris-tarayici.js"]) {
  const r = yayil(degisen);
  console.log(ad(degisen).padEnd(18) +
    (r.tamYukleme ? "tam yukleme" : "sicak degisim").padEnd(14) +
    r.gecersiz.length + "/" + cizge.size + "  " + r.gecersiz.map(ad).join(", ") +
    (r.durum.length ? "   [durum sifirlanir: " + r.durum.map(ad).join(", ") + "]" : ""));
}
```

```
$ node sicak.mjs
cizge: 13 modul, kabul siniri: sablon.js, panel.js, grafik.js
degisen           sonuc         yeniden degerlendirilen kume
grafik.js         sicak degisim 1/13  grafik.js
panel.js          sicak degisim 1/13  panel.js
bicim.js          sicak degisim 4/13  bicim.js, sablon.js, disa-aktarma.js, panel.js
birim.js          sicak degisim 10/13  birim.js, grafik.js, uyari.js, arac.js, tahmin.js, olcek-kaydi.js, panel.js, bicim.js, sablon.js, disa-aktarma.js
kayit.js          sicak degisim 7/13  kayit.js, arac.js, olcek-kaydi.js, bicim.js, sablon.js, disa-aktarma.js, panel.js   [durum sifirlanir: kayit.js]
giris-tarayici.js tam yukleme   1/13  giris-tarayici.js
```

Tablo üç şey söyler.

**Sınırın altındaki değişiklik ucuzdur.** Grafik ve panel modülleri kendi güncellemelerini
kabul ettiği için yalnız kendileri yeniden değerlendirilir; on üç modülün on ikisi
dokunulmadan kalır.

**Yaprak modüldeki değişiklik pahalıdır.** Birim modülü çizgede aşağıdadır ve onu doğrudan
ya da dolaylı olarak isteyen dokuz modül vardır; hepsi yeniden değerlendirilir. Ortak
yardımcı modüllere yapılan küçük düzenlemeler bu yüzden yavaş görünür.

**Kabul sınırının yeri kararı belirler.** Sınırlar görünüm sınırlarına konur, çünkü bir
görünüm kendi çıktısını yeniden üretebilir. Yardımcı modüller sınır olamaz: değerlerini
isteyenlerin elindeki bağları güncelleyemezler.

#### Sıcak Değişimin Doğruluk Koşulu

Sıcak değişim bir iyileştirme değil, bir risktir; doğru uygulanmadığında geliştiricinin
gördüğü durumla soğuk yüklemede oluşacak durum ayrışır.

Birinci tehlike **durum sıfırlanmasıdır.** Modül düzeyinde durum tutan bir modül yeniden
değerlendirildiğinde o durum kaybolur. Tablodaki son sütun bunu işaretliyor: kayıt modülü
güncellendiğinde biçimlendirici kaydı boşalır ve onu dolduran yan etkili modül de yeniden
çalışmazsa arayüz eksik davranır. Ağaç sarsma dersindeki sessiz davranış kaybının aynısı,
bu kez geliştirme sırasında ortaya çıkar.

İkinci tehlike **eski kapanışlardır.** Bir modülün eski sürümünde kaydedilmiş bir olay
dinleyicisi ya da zamanlayıcı, modül değiştirildikten sonra da çalışmaya devam eder. Kabul
sınırı, yeni sürüme geçmeden önce eskisinin bıraktıklarını temizlemekle yükümlüdür.

Üçüncü tehlike **birikmiş sapmadır.** Onlarca sıcak değişimden sonraki durum, aynı kaynağın
soğuk yüklemesiyle oluşacak durumdan farklı olabilir. Kural, şüphe duyulan her noktada
sayfayı elle yenilemek ve doğrulamayı soğuk yükleme üzerinde yapmaktır.

Bu üç tehlike, sıcak değişimin uygulanamadığı durumlarda tam yüklemeye düşmenin neden bir
kusur değil bir güvence olduğunu açıklar.

#### Geliştirme ile Yayın Arasındaki Uçurum

Geliştirme kipinin düşürdüğü her adım, iki kip arasında bir davranış farkı olasılığı
yaratır. Ağaç sarsma kapalıysa, elenmemesi gereken bir modülün elendiği yayında ortaya
çıkar. Modüller ayrı dosyalar hâlinde sunuluyorsa, paketlemenin ürettiği değerlendirme
sırası farkı yayında ortaya çıkar. Küçültme kapalıysa, ada dayalı bir davranış yayında
kırılır.

Bu farkların hepsinin ortak bir sonucu vardır: **geliştirme kipinde çalışması, yayın
kipinde çalıştığının kanıtı değildir.** Bu yüzden yayın çıktısını yerelde çalıştırıp
denemek geliştirme akışının bir parçasıdır ve dağıtım konusundaki önizleme dağıtımları
dersi bu denemeyi her değişikliğe yayacaktır.

#### Özet

- Geliştirme kipi paketleme, ağaç sarsma, küçültme ve içerik karmasını düşürür; belirteç
  çözümü, sözdizim çevirisi ve kaynak haritası kalır.
- Çizgeyi tarayıcının modül yükleyicisi dolaşır; sunucu tek bir modülü dönüştürüp verir,
  maliyet çizge boyutundan bağımsızlaşır.
- Örnekte ilk yükleme on üç modülün dokuzunu ister, panel üç modül daha ekler, dinamik
  sınırın ardındaki modül hiç istenmez; doğrulayıcı değişmeyen modülleri gövdesiz yanıtla
  geçiştirir.
- Sıcak değişim, geçersiz kılmanın isteyenlere doğru yayılmasıdır; kabul sınırı yayılımı
  durdurur, sınır bulunamazsa tam yükleme yapılır.
- Sınırın altındaki değişiklik bir modülü, ortak bir yardımcı modüldeki değişiklik on
  modülü yeniden değerlendirtir; sınırlar görünüm sınırlarına konur.
- Sıcak değişim durum sıfırlanması, eski kapanışlar ve birikmiş sapma riskleri taşır; şüphe
  hâlinde soğuk yükleme doğrulamanın ölçüsüdür.

#### Sonraki Adım

Geliştirme ve yayın kipleri yalnız hızda değil, çalıştıkları ortamda da ayrılır. Yerelde
istasyon verisi bir deneme ucundan, yayında gerçek ölçüm hattından gelir; günlük ayrıntısı
yerelde açık, yayında kapalıdır; kimlik doğrulama uçları farklı adreslerdedir. Bu farkların
kaynağa gömülmesi kabul edilemez, çünkü aynı kaynaktan iki ayrı çıktı üretilir. Farkı
taşıyan şey derleme zamanı değişkenleridir. Sonraki ders bu değişkenlerin çıktıya nasıl
girdiğini gösterir ve tek kritik kuralı kurar: istemciye giden bir değişken herkese açıktır.

### Ortam Yapılandırması

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/ortam-yapilandirmasi

Derleme zamanı değişkenlerinin çıktıya nasıl girdiği; ikamenin ürettiği sabitler, istemci paketine geçen her değerin herkese açık olması, geçebilen ile geçemeyen değer ayrımının denetlenebilir bir kurala dönüştürülmesi ve çalışma zamanı yapılandırmasının ödünleşimi.

Bir önceki ders derlemenin iki kipini ayırdı, ama iki kipin de ortak bir varsayımı vardı:
kaynak aynı. Kuzey Yamaç Ölçüm İstasyonu arayüzü yerelde bir deneme ucundan, yayında gerçek
ölçüm hattından veri okur; günlük ayrıntısı yerelde açık, yayında kapalıdır; arşiv yazma
işlemi yerelde geçici bir dizine, yayında kalıcı bir depoya gider. Bu farkların kaynağa
yazılması aynı kaynaktan iki ayrı çıktı üretilmesi demektir ve o noktada tek bir kaynak
ağacından söz edilemez.

Farkı taşıyan şey **derleme zamanı değişkenleridir (build-time variables)**. Bu ders önce
bu değişkenlerin çıktıya nasıl girdiğini gösterir, sonra tek kritik kuralı kurar: istemci
paketine giren bir değer herkese açıktır.

#### İki Yapılandırma Anı

Sunucuda çalışan bir program yapılandırmasını çalışma zamanında okur. Süreç başlarken
ortamdan bir adı sorar, değeri alır, kullanır. Değer değiştiğinde süreç yeniden başlatılır;
program yeniden derlenmez. Ortam değişkenlerinin bir dosyadan — sürüm denetimine girmeyen
bir `.env` dosyasından — yüklenmesi bu okumanın kaynağını değiştirir, zamanını değiştirmez.

Tarayıcıda çalışan kodun böyle bir kaynağı yoktur. Sayfa bir süreç ortamının içinde açılmaz;
okuyabileceği tek şey kendisine verilmiş baytlardır. Yapılandırmanın istemci koduna girmesi
için iki yol kalır ve ikisi ayrı anlarda çalışır.

**Derleme zamanı ikamesi.** Kaynaktaki bir yer tutucu, derleme sırasında değerin kendisiyle
değiştirilir. Çıktıda değişken yoktur; sabit vardır. Değer değiştiğinde yeniden derleme
gerekir.

**Çalışma zamanı okuması.** Değer çıktıya değil, belgeye ya da ayrı bir uca konur; istemci
onu ilk isteğinde alır. Değer değiştiğinde yeniden derleme gerekmez, ama bir okuma daha
gerekir.

Bu dersin ana konusu birincisidir, çünkü ikincisinin bir ön koşulu vardır: hangi değerin
istemciye hiç verilmeyeceğine önce karar edilmiş olmalıdır.

#### İkamenin Yaptığı Şey

İkame bir metin işlemidir. Aşağıdaki dönüştürücü, kaynaktaki `ORTAM.<AD>` yer tutucularını
ortam tablosundaki değerlerin kod olarak yazılmış hâliyle değiştirir ve tek bir paket üretir.

```js
// ikame.mjs — derleme zamani ortam ikamesi ve ciktida deger aramasi.
import { mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs";

// Kaynak agac: uc modul, ucu de ORTAM.<AD> bicimindeki bir yer tutucu okuyor.
const KAYNAK = {
  "olcum-istemcisi.js":
    'const UC = ORTAM.KUZEY_OLCUM_UCU;\n' +
    'export const olcumleriGetir = (istasyon) =>\n' +
    '  fetch(UC + "/istasyon/" + istasyon).then((y) => y.json());\n',
  "gunluk.js":
    'const SIRA = { ayrinti: 0, bilgi: 1, uyari: 2, hata: 3 };\n' +
    'const DUZEY = ORTAM.KUZEY_GUNLUK_DUZEYI;\n' +
    'export const yaz = (duzey, ileti) =>\n' +
    '  SIRA[duzey] >= SIRA[DUZEY] ? console.log(duzey + ": " + ileti) : undefined;\n',
  "arsiv-yazici.js":
    'const ANAHTAR = ORTAM.KUZEY_YAZMA_ANAHTARI;\n' +
    'export const arsivle = (kayit) =>\n' +
    '  fetch("/arsiv", { method: "POST", headers: { "X-Anahtar": ANAHTAR },\n' +
    '                    body: JSON.stringify(kayit) });\n',
};

// .env dosyasinin icerigi: ad=deger satirlari, surume girmez.
const ENV = [
  "KUZEY_OLCUM_UCU=https://olcum.kuzey-yamac.example/v1",
  "KUZEY_GUNLUK_DUZEYI=uyari",
  "KUZEY_YAZMA_ANAHTARI=yaz-9f21c4a7e08b",
].join("\n") + "\n";

const cozumle = (metin) => Object.fromEntries(metin.split("\n")
  .filter((s) => s.includes("="))
  .map((s) => [s.slice(0, s.indexOf("=")).trim(), s.slice(s.indexOf("=") + 1).trim()]));

const ortam = cozumle(ENV);

// Ikame: ORTAM.<AD>, degerin kaynak koda yazilmis sabiti ile degistirilir.
const ikameEt = (kaynak) => kaynak.replace(/ORTAM\.([A-Z0-9_]+)/g,
  (tam, ad) => (ad in ortam ? JSON.stringify(ortam[ad]) : tam));

rmSync("cikti", { recursive: true, force: true });
mkdirSync("cikti", { recursive: true });
mkdirSync("kaynak", { recursive: true });
writeFileSync(".env.yayin", ENV);

let paket = "";
const degisen = [];
for (const [ad, govde] of Object.entries(KAYNAK)) {
  writeFileSync("kaynak/" + ad, govde);
  const cikti = ikameEt(govde);
  paket += "// " + ad + "\n" + cikti + "\n";
  govde.split("\n").forEach((satir, i) => {
    if (satir.includes("ORTAM.")) degisen.push([ad, cikti.split("\n")[i]]);
  });
}
writeFileSync("cikti/paket.js", paket);

console.log("-- ikame edilmis satirlar --");
for (const [ad, satir] of degisen) console.log("  " + ad.padEnd(20) + satir);

const govde = readFileSync("cikti/paket.js", "utf8");
console.log("-- cikti paketinde deger aramasi --");
for (const [ad, deger] of Object.entries(ortam))
  console.log("  " + ad.padEnd(22) + (govde.includes(deger) ? "GORUNUYOR" : "yok"));
```

Betiğin kendi araması dışında, üretilen dosyada bağımsız bir arama da yapılır.

```bash
#!/usr/bin/env bash
# Ikameyi calistirir, sonra uretilen paketi dogrudan tarar.
node ikame.mjs
echo "=== bagimsiz arama ==="
grep -c 'yaz-9f21c4a7e08b' cikti/paket.js
grep -n 'X-Anahtar' cikti/paket.js
```

```
-- ikame edilmis satirlar --
  olcum-istemcisi.js  const UC = "https://olcum.kuzey-yamac.example/v1";
  gunluk.js           const DUZEY = "uyari";
  arsiv-yazici.js     const ANAHTAR = "yaz-9f21c4a7e08b";
-- cikti paketinde deger aramasi --
  KUZEY_OLCUM_UCU       GORUNUYOR
  KUZEY_GUNLUK_DUZEYI   GORUNUYOR
  KUZEY_YAZMA_ANAHTARI  GORUNUYOR
=== bagimsiz arama ===
1
15:  fetch("/arsiv", { method: "POST", headers: { "X-Anahtar": ANAHTAR },
```

Çıktının söylediği şey teknik olarak sıradan, sonuçları bakımından değil: üç değerin üçü de
paketin baytlarında düz metin olarak durur. Yazma anahtarı, kaynakta bir ad arkasındaydı;
çıktıda bir dizgi sabitidir.

Bu sonuç bir kusur değil, ikamenin tanımıdır. Çıktıda değişkeni çözecek bir düzenek
bulunmadığı için değerin oraya yazılmış olması gerekir. Aynı nedenle, ikame edilen değeri
çıktıda gizleyecek bir derleme adımı da yoktur:

- **Küçültme değerleri değiştirmez.** Ad kısaltma yerel adları değiştirir; dizgi sabitleri
  olduğu gibi kalır. Üçüncü dersteki ağaç sarsma da bir dizgiyi elemek için bir neden bulmaz.
- **Kodlama gizleme değildir.** Değeri taban 64 ile ya da basit bir karıştırmayla yazmak,
  çözücüsü aynı pakette bulunduğu için değeri kaldırmaz; adım sayısını artırır.
- **Kaynak haritası ikinci bir kopyadır.** Yayında herkese açık sunulan bir kaynak haritası,
  ikame edilmiş satırların özgün hâlini de taşır.

Buradan dersin kuralı çıkar: **istemci paketine giren bir değer, sayfayı açan herkesin
elindedir.** İkameyle giren de, çalışma zamanında verilen de aynı durumdadır.

#### Gizli Değer Sınırı

Kural bir yasak listesi değil, bir sınıflandırmadır. Her yapılandırma değeri iki kümeden
birine girer ve ayrım tek bir soruyla yapılır: **bu değeri bilen biri, bilmeyen birinin
yapamayacağı bir iş yapabilir mi?**

Yanıt "hayır" ise değer istemciye geçebilir. Bu küme genel yapılandırmadır: açık uçların
adresleri, günlük düzeyi, özellik anahtarlarının açık/kapalı durumu, sürüm etiketi, genel
kiracı ya da site kimliği, yerel ayar listesi. Bunları bilmek yeni bir yetki vermez; zaten
gözlemlenebilir olan şeyi söyler.

Yanıt "evet" ise değer istemciye geçemez. Bu küme gizli değerlerdir: sunucu tarafı yazma
anahtarları, imzalama sırları, veri deposu bağlantı bilgileri, yönetim uçlarının kimlik
bilgileri, üçüncü taraf hizmetlerin gizli anahtarları. Bunları bilen biri, sunucunun
yetkisiyle iş yapabilir.

Sınırın iki sonucu vardır ve ikisi de sık ihmal edilir.

**İhtiyaç bir istisna üretmez.** İstemcinin bir gizli değere ihtiyaç duyduğu görülüyorsa, ihtiyaç
duyulan şey değer değil, o değerle yapılan iştir. Doğru çözüm işi sunucuya taşımaktır: istemci
kendi kökenindeki bir uca çağrı yapar, yetkiyi oturum taşır ve gizli değer sunucuda kalır.
Yukarıdaki arşiv yazıcısının doğru biçimi budur.

**Sızmış bir değer geri alınamaz.** Çıktı indirilmiş, ara önbelleklere ve tarayıcı
önbelleklerine yerleşmiştir; beşinci derste eski adların bir süre daha yaşadığı da
söylenmişti. Yayını geri almak değeri geri getirmez. Tek karşılık, değerin geçersiz
kılınması ve yenisiyle değiştirilmesidir. Bu yüzden sınır, sızıntıdan sonra düzeltilecek bir
şey değil, derlemeyi durduran bir denetimdir.

Aynı gerekçe derlemenin yapıldığı yere de uzanır. Gizli değerler bir sürekli tümleştirme
hattında ortama verilir; hattın günlüğüne yazılan her satır, o hattın çıktısını görebilen
herkese açıktır. İkameyi yapan aracın hangi adları çıktıya koyduğunu günlüğe basması,
sızıntının en sessiz biçimidir.

#### Sınırı Denetlemek

Sınıflandırma bir belgede kaldığı sürece uygulanmaz. Denetlenebilir olması için üç şey
gerekir: adların kapsamının tek bir yerde bildirilmesi, bildirimin adlandırmaya yansıması ve
çıktının bildirime karşı sınanması.

Aşağıdaki denetleyici bunu beş kuralla yapar. `K1` bildirilmemiş adı, `K2` istemci
kaynağında okunan sunucu kapsamlı adı, `K3` açık önek taşımayan istemci adını, `K4` çıktı
baytlarında bulunan bir gizli değeri, `K5` ortamda karşılığı olmadığı için çıktıda kalmış
yer tutucuyu bildirir.

```js
// denetci.mjs — istemciye acik degisken ile sunucuda kalan degiskeni ayiran denetleyici.
// Kullanim: node denetci.mjs ilk | node denetci.mjs duzeltilmis
const ONEK = "KUZEY_ACIK_";

// Bildirim: her adin kapsami tek bir yerde yazilir. Denetim bu tabloya bakar.
const BILDIRIM = {
  KUZEY_ACIK_OLCUM_UCU: "istemci",
  KUZEY_ACIK_GUNLUK_DUZEYI: "istemci",
  KUZEY_ACIK_SURUM_ETIKETI: "istemci",
  KUZEY_OLCUM_UCU: "istemci",
  KUZEY_GUNLUK_DUZEYI: "istemci",
  KUZEY_YAZMA_ANAHTARI: "sunucu",
  KUZEY_IMZALAMA_SIRRI: "sunucu",
};

const YAZ = 'const ANAHTAR = ORTAM.KUZEY_YAZMA_ANAHTARI;\n' +
  'export const arsivle = (kayit) => istek("/arsiv", ANAHTAR, kayit);\n';

const SURUM = {
  ilk: {
    istemci: {
      "olcum-istemcisi.js": 'const UC = ORTAM.KUZEY_OLCUM_UCU;\n',
      "gunluk.js": 'const DUZEY = ORTAM.KUZEY_GUNLUK_DUZEYI;\n',
      "dipnot.js": 'export const dipnot = () => "surum " + ORTAM.KUZEY_ACIK_SURUM_ETIKETI;\n',
      "arsiv-yazici.js": YAZ,
    },
    sunucu: {},
    env: {
      KUZEY_OLCUM_UCU: "https://olcum.kuzey-yamac.example/v1",
      KUZEY_GUNLUK_DUZEYI: "uyari",
      KUZEY_YAZMA_ANAHTARI: "yaz-9f21c4a7e08b",
      KUZEY_IMZALAMA_SIRRI: "imza-4d70b1cc52ae",
    },
  },
  duzeltilmis: {
    istemci: {
      "olcum-istemcisi.js": 'const UC = ORTAM.KUZEY_ACIK_OLCUM_UCU;\n',
      "gunluk.js": 'const DUZEY = ORTAM.KUZEY_ACIK_GUNLUK_DUZEYI;\n',
      "dipnot.js": 'export const dipnot = () => "surum " + ORTAM.KUZEY_ACIK_SURUM_ETIKETI;\n',
      // Arsiv yazimi ayni kokendeki uca tasindi; anahtar istemci kaynaginda gecmiyor.
      "arsiv-gonderici.js": 'export const arsivle = (kayit) => istek("/uc/arsiv", kayit);\n',
    },
    sunucu: { "arsiv-yazici.js": YAZ },
    env: {
      KUZEY_ACIK_OLCUM_UCU: "https://olcum.kuzey-yamac.example/v1",
      KUZEY_ACIK_GUNLUK_DUZEYI: "uyari",
      KUZEY_ACIK_SURUM_ETIKETI: "2026.03",
      KUZEY_YAZMA_ANAHTARI: "yaz-9f21c4a7e08b",
      KUZEY_IMZALAMA_SIRRI: "imza-4d70b1cc52ae",
    },
  },
};

const okunanlar = (kaynaklar) => [...new Set(Object.values(kaynaklar)
  .flatMap((g) => [...g.matchAll(/ORTAM\.([A-Z0-9_]+)/g)].map(([, ad]) => ad)))];

const paketle = (kaynaklar, env) => Object.values(kaynaklar).join("")
  .replace(/ORTAM\.([A-Z0-9_]+)/g, (tam, ad) => (ad in env ? JSON.stringify(env[ad]) : tam));

function denetle(surum) {
  const { istemci, sunucu, env } = SURUM[surum];
  const paket = paketle(istemci, env);
  const bulgu = [];
  const ekle = (kural, ad, ileti) => bulgu.push([kural, ad, ileti]);

  for (const ad of okunanlar(istemci)) {
    if (!(ad in BILDIRIM)) ekle("K1", ad, "bildirimde yok");
    else if (BILDIRIM[ad] === "sunucu") ekle("K2", ad, "sunucu kapsamli ad istemcide okunuyor");
    else if (!ad.startsWith(ONEK)) ekle("K3", ad, "istemci kapsamli ad " + ONEK + " onekini tasimiyor");
    if (!(ad in env)) ekle("K5", ad, "ortamda tanimli degil, yer tutucu ciktida kaldi");
  }
  for (const [ad, kapsam] of Object.entries(BILDIRIM))
    if (kapsam === "sunucu" && ad in env && paket.includes(env[ad]))
      ekle("K4", ad, "degeri istemci paketinin baytlarinda bulundu");

  console.log("surum: " + surum);
  console.log("  istemci paketi: " + Object.keys(istemci).length + " modul, okunan ad: " +
    okunanlar(istemci).join(", "));
  console.log("  sunucu paketi : " + Object.keys(sunucu).length + " modul, okunan ad: " +
    (okunanlar(sunucu).join(", ") || "-"));
  for (const [kural, ad, ileti] of bulgu)
    console.log("  " + kural + "  " + ad.padEnd(26) + ileti);
  console.log("  ihlal: " + bulgu.length);
  return bulgu.length;
}

process.exitCode = denetle(process.argv[2]) > 0 ? 1 : 0;
```

```bash
#!/usr/bin/env bash
# Iki surumu de denetler ve cikis kodlarini yazar.
node denetci.mjs ilk;          echo "cikis kodu: $?"
node denetci.mjs duzeltilmis;  echo "cikis kodu: $?"
```

```
surum: ilk
  istemci paketi: 4 modul, okunan ad: KUZEY_OLCUM_UCU, KUZEY_GUNLUK_DUZEYI, KUZEY_ACIK_SURUM_ETIKETI, KUZEY_YAZMA_ANAHTARI
  sunucu paketi : 0 modul, okunan ad: -
  K3  KUZEY_OLCUM_UCU           istemci kapsamli ad KUZEY_ACIK_ onekini tasimiyor
  K3  KUZEY_GUNLUK_DUZEYI       istemci kapsamli ad KUZEY_ACIK_ onekini tasimiyor
  K5  KUZEY_ACIK_SURUM_ETIKETI  ortamda tanimli degil, yer tutucu ciktida kaldi
  K2  KUZEY_YAZMA_ANAHTARI      sunucu kapsamli ad istemcide okunuyor
  K4  KUZEY_YAZMA_ANAHTARI      degeri istemci paketinin baytlarinda bulundu
  ihlal: 5
cikis kodu: 1
surum: duzeltilmis
  istemci paketi: 4 modul, okunan ad: KUZEY_ACIK_OLCUM_UCU, KUZEY_ACIK_GUNLUK_DUZEYI, KUZEY_ACIK_SURUM_ETIKETI
  sunucu paketi : 1 modul, okunan ad: KUZEY_YAZMA_ANAHTARI
  ihlal: 0
cikis kodu: 0
```

İlk sürümün beş bulgusu üç ayrı kusur sınıfını gösterir.

**Adlandırma kusuru.** İki `K3` bulgusu bir sızıntı değildir; sızıntıyı görünmez kılan
koşuldur. Açık önek, bir adı okuyan geliştiriciye o değerin çıktıya gireceğini adın kendisi
üzerinden söyler. Önek yoksa `K2` ile `K3` arasındaki fark bir dikkat meselesine iner.

**Eksik yapılandırma.** `K5`, ortamda karşılığı olmayan bir adın ikame edilmeden çıktıda
kaldığını bildirir. Bu durumda çıktıda tanımsız bir başvuru kalır ve hata çalışma zamanında,
o satır yürütüldüğünde ortaya çıkar. Derleme zamanında yakalanabilecek bir kusurun kullanıcı
tarafında ortaya çıkması, denetimin var oluş nedenidir.

**Sınır ihlali.** `K2` ile `K4` aynı sorunun iki farklı yerden görülmüş hâlidir: biri
kaynağa, öteki çıktının baytlarına bakar. İkisi birden gerekir. Kaynak denetimi, ada
dokunmadan değeri elle yazan bir kodu yakalayamaz; bayt denetimi ise adın nereden geldiğini
söyleyemez.

Düzeltilmiş sürümde arşiv yazıcısı istemci kümesinden çıkarılıp sunucu kümesine alınmıştır.
İstemcide kalan modül aynı kökendeki bir uca çağrı yapar; anahtar hiçbir aşamada istemci
kaynağına girmez.

#### Yeniden Derlemeyi Gerektirmeyen Yapılandırma

Derleme zamanı ikamesinin bir bedeli vardır ve bu bedel dağıtım konusunun sorusudur: ikame
edilen her değer, çıktıyı o ortama bağlar. Beş ortam varsa beş ayrı derleme çıktısı olur ve
önizlemede sınanan baytlar, yayına giden baytlar değildir.

Çalışma zamanı yapılandırması bu bağı çözer. Ortama göre değişen değerler pakete girmez;
belgeye küçük bir bölüm olarak ya da ayrı bir uca konur, istemci onları okuyup kullanır.
Aynı derleme çıktısı her ortamda çalışır.

Ödünleşim üç ölçüde okunur. **Derleme sayısı** ortam sayısı kadardan bire iner. Buna karşılık
**ilk görünüme kadar okunması gereken kaynak sayısı** artar; değer ayrı bir uçtan geliyorsa
bu bir ağ turu daha demektir. Değer belgeye gömülürse tur eklenmez, ama **belgenin
önbelleklenebilirliği** düşer: beşinci dersin politikasında belge zaten her kullanımda
yeniden doğrulanıyordu, dolayısıyla bu iki karar birbiriyle uyumludur.

Seçim ölçütü şudur: bir değer ortamlar arasında değişiyor ve aynı çıktının birden çok ortamda
çalışması isteniyorsa çalışma zamanında verilir; değer bütün ortamlarda aynıysa ya da
değerinin derleme kararlarını etkilemesi gerekiyorsa — örneğin ölü kod elemesinin bir
özelliği tümüyle çıkarabilmesi için — derleme zamanında ikame edilir.

İki yolun **değiştirmediği** tek şey sınırdır. Çalışma zamanında verilen bir değer de
istemciye ulaştığı anda herkese açıktır; onu belgeye yazmak, pakete yazmaktan farklı bir
güvenlik durumu üretmez.

#### Özet

- Tarayıcıda çalışan kodun süreç ortamı yoktur; yapılandırma ona ya derleme zamanında ikame
  edilerek ya da çalışma zamanında okutularak ulaşır.
- İkame, yer tutucuyu değerin sabiti ile değiştirir; örnekte üç değerin üçü de çıktı
  paketinin baytlarında düz metin olarak bulunur.
- Küçültme, kodlama ve ad kısaltma değerleri gizlemez; kaynak haritası ikinci bir kopya
  taşır. İstemci paketine giren değer, sayfayı açan herkesin elindedir.
- Ayrım tek soruyla yapılır: değeri bilen biri, bilmeyen birinin yapamayacağı bir iş
  yapabiliyorsa değer gizlidir ve istemciye geçemez; iş sunucuya taşınır.
- Sınır denetlenebilir olmalıdır: kapsam bildirimi, açık önek ve hem kaynağa hem çıktı
  baytlarına bakan bir denetim, ihlalde derlemeyi durdurur.
- Çalışma zamanı yapılandırması derleme sayısını bire indirir ama bir okuma ekler; sızmış
  bir gizli değer ise ancak geçersiz kılınarak karşılanır.

#### Sonraki Adım

Derleme çıktısı artık belirlenmiş durumda: parçaları, varlıkları, karmalı adları ve ortamına
bağlı sabitleriyle bir dizin. Bu dizin bir makinenin diskinde durduğu sürece kimsenin işine
yaramaz. Sıradaki soru, o baytların kullanıcının tarayıcısına hangi yolla ulaştığıdır.
En yalın yol, çıktıyı olduğu gibi kopyalayıp kullanıcılara yakın duran düğümlerden
sunmaktır: hiçbir sunucu süreci çalışmaz, her istek bir dosyaya karşılık gelir. Sonraki ders
bu yolu kurar, kenar önbelleğinde isabet ile ıskayı ölçer ve beşinci dersteki ada göre
önbellek politikasının isabet oranına dönüşümünü hesaplar.

## Dağıtım

Derleme çıktısının kullanıcıya ulaştırılması; içerik dağıtım ağı üzerinden statik yayın, çalışma zamanı gerektiren dağıtım biçimleri, değişiklik başına önizleme ortamları, aşamalı yayın ile geri alma ve web dışı hedeflere paketleme.

### Statik Barındırma

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/statik-barindirma

Derleme çıktısının sunucu süreci olmadan yayınlanması; kenar önbelleğinin isabet, ıska, doğrulama ve yenilenme durumlarının ölçülmesi, ada göre önbellek politikasının istek ve bayt isabet oranına çevrimi, geçersiz kılma ve yol çözümü.

Derleme çıktısı artık belirlenmiş durumda: parçaları, işlenmiş varlıkları, içerik karmalı
adları ve ortamına bağlı sabitleriyle bir dizin. Bu dizin, üretildiği makinenin diskinde
durduğu sürece kimsenin işine yaramaz.

Bu ders çıktının kullanıcıya ulaşmasının en yalın yolunu kurar: dizini olduğu gibi
kopyalamak ve kullanıcılara yakın duran düğümlerden sunmak. Sorusu şudur — hiçbir sunucu
süreci çalışmadığında, hız ve tazelik nereden gelir?

#### Sunucu Süreci Olmayan Yayın

Statik barındırmada yayın, dosyaların bir kopyasından ibarettir. Gelen her istek bir yola
bakar, o yola karşılık gelen dosyayı bulur ve baytlarını gönderir. İstek başına çalışan bir
program yoktur.

Bunun üç sonucu vardır.

**İstek başına maliyet hemen hemen sıfırdır.** Sunucuda Oluşturma dersindeki istek başına
maliyet kalemi burada yoktur; ölçeklenmenin yolu daha güçlü makine değil, dosyanın daha çok
yere kopyalanmasıdır.

**Sunulan tarafta gizli değer bulunmaz.** Bir önceki dersin sınırı burada kendiliğinden
korunur: sunulan şey istemci paketinin baytlarıdır ve o baytlar zaten herkese açıktır.

**Yanıt kullanıcıya göre değişemez.** Aynı yola gelen iki istek aynı baytları alır. Kişiye
özel içerik, yetkiye bağlı içerik ve isteğin kendisine bakarak verilen kararlar bu modelde
üretilemez.

Birincisi modelin gücünü, öteki ikisi sınırını verir. Gücün kullanılabilmesi kopyaların
kullanıcıya yakın durmasına bağlıdır; Kenar Oluşturma dersindeki mesafe tablosu bunu
ölçmüştü. Kopyayı yakında tutan katman **içerik dağıtım ağıdır** ve statik barındırmada kod
çalıştırmaz, yalnız bayt saklar.

#### Kenar Önbelleğinin Dört Durumu

Kenar düğümünün elindeki kopya, kaynaktaki dosyayla dört ayrı ilişkide olabilir. Aşağıdaki
iki sunucu bu dört durumu ayırt edilebilir kılar. Kaynak sunucuda coğrafi uzaklık, yanıttan
önce eklenen sabit bir gecikmeyle modellenmiştir.

```js
// kaynak-sunucu.mjs — yayin dizinini sunan kaynak. Cografi uzaklik sabit gecikmedir.
import { createServer } from "node:http";
import { createHash } from "node:crypto";

const UZAKLIK = 80; // ms, kaynaga gidip donmenin bedeli
const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

const DEGISMEZ = "public, max-age=31536000, immutable";
const BELGE_POLITIKASI = "public, no-cache";

// Yayin dizini: karmali adli varliklar (Onbellek Kirma dersinin adlandirmasi) ve tek belge.
const VARLIKLAR = new Map([
  ["/varlik/istasyon-7e1d6934.css",
   [".olcum { font-variant-numeric: tabular-nums; }\n", "text/css; charset=utf-8"]],
  ["/varlik/giris-5f45daac.js",
   ['export const ac = () => import("/varlik/panel-c3924741.js");\n',
    "text/javascript; charset=utf-8"]],
]);

let yayinNo = 1; // yeni yayin bu sayiyi artirir, belgenin icerigi degisir
const belge = () =>
  '<!doctype html><html lang="tr"><head>' +
  '<link rel="stylesheet" href="/varlik/istasyon-7e1d6934.css">' +
  '</head><body><h1>Kuzey Yamac Olcum Istasyonu</h1>' +
  "<p>yayin " + yayinNo + "</p></body></html>\n";

const etiketle = (govde) =>
  '"' + createHash("sha256").update(govde).digest("hex").slice(0, 12) + '"';

function coz(yol) {
  if (VARLIKLAR.has(yol)) {
    const [govde, tur] = VARLIKLAR.get(yol);
    return { govde, tur, politika: DEGISMEZ };
  }
  // Karmali ad bulunamadiysa gercek bir 404'tur; yedek belge verilmez.
  if (yol.startsWith("/varlik/")) return null;
  // Geri kalan her yol belgeye cozulur: yol cozumu istemcide yapilir.
  return { govde: belge(), tur: "text/html; charset=utf-8", politika: BELGE_POLITIKASI };
}

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  await bekle(UZAKLIK);
  if (istek.method === "POST" && istek.url === "/yonetim/yayin") {
    yayinNo += 1;
    yanit.writeHead(200, { "Content-Type": "text/plain" }).end("yayin " + yayinNo + "\n");
    return;
  }
  const kayit = coz(istek.url);
  if (kayit === null) {
    yanit.writeHead(404, { "Cache-Control": "public, max-age=60" }).end();
    return;
  }
  const etiket = etiketle(kayit.govde);
  const ortak = { ETag: etiket, "Cache-Control": kayit.politika };
  if (istek.headers["if-none-match"] === etiket) {
    yanit.writeHead(304, ortak).end();
    return;
  }
  yanit.writeHead(200, {
    ...ortak,
    "Content-Type": kayit.tur,
    "Content-Length": Buffer.byteLength(kayit.govde),
  }).end(kayit.govde);
}).listen(8180, "127.0.0.1", () => console.log("kaynak: 127.0.0.1:8180"));
```

```js
// kenar-onbellegi.mjs — kullaniciya yakin duran, yalniz bayt saklayan kenar dugumu.
// Kod calistirmaz; Cache-Control ve ETag ne diyorsa onu uygular.
import { createServer } from "node:http";

const KAYNAK = "http://127.0.0.1:8180";
const onbellek = new Map(); // yol -> { govde, tur, etiket, politika, tazeSonu }
const saniye = () => Date.now() / 1000;

// Tazelik suresi yalniz Cache-Control'den okunur; no-cache sifir tazelik demektir.
function tazelik(politika) {
  if (!politika || /no-store|no-cache/.test(politika)) return 0;
  const e = /max-age=(\d+)/.exec(politika);
  return e ? Number(e[1]) : 0;
}

const gonder = (yanit, kayit, durum) => {
  yanit.writeHead(200, {
    "Content-Type": kayit.tur,
    "Content-Length": Buffer.byteLength(kayit.govde),
    "Cache-Control": kayit.politika,
    ETag: kayit.etiket,
    "X-Onbellek": durum,
  }).end(kayit.govde);
};

createServer(async (istek, yanit) => {
  yanit.sendDate = false;

  // Gecersiz kilma: kenardaki kopya elle silinir, sonraki istek kaynaga gider.
  if (istek.method === "POST" && istek.url.startsWith("/yonetim/gecersiz-kil")) {
    const hedef = new URL(istek.url, "http://kenar").searchParams.get("yol");
    const vardi = onbellek.delete(hedef);
    yanit.writeHead(200, { "Content-Type": "text/plain" })
      .end((vardi ? "silindi : " : "yoktu  : ") + hedef + "\n");
    return;
  }

  const kayit = onbellek.get(istek.url);
  if (kayit && kayit.tazeSonu > saniye()) {
    gonder(yanit, kayit, "isabet");
    return;
  }

  const baslik = kayit ? { "If-None-Match": kayit.etiket } : {};
  const cevap = await fetch(KAYNAK + istek.url, { headers: baslik });

  if (cevap.status === 304) {
    kayit.politika = cevap.headers.get("cache-control") ?? kayit.politika;
    kayit.tazeSonu = saniye() + tazelik(kayit.politika);
    gonder(yanit, kayit, "dogrulandi");
    return;
  }
  if (cevap.status !== 200) {
    onbellek.delete(istek.url);
    yanit.writeHead(cevap.status, { "X-Onbellek": "kaynaktan" }).end();
    return;
  }

  const yeni = {
    govde: await cevap.text(),
    tur: cevap.headers.get("content-type"),
    etiket: cevap.headers.get("etag"),
    politika: cevap.headers.get("cache-control"),
  };
  yeni.tazeSonu = saniye() + tazelik(yeni.politika);
  onbellek.set(istek.url, yeni);
  gonder(yanit, yeni, kayit ? "yenilendi" : "iska");
}).listen(8181, "127.0.0.1", () => console.log("kenar: 127.0.0.1:8181"));
```

Aşağıdaki betik bu iki dosyayla aynı dizinde çalıştırılır. Bağlantı noktaları 8180 ve 8181
keyfîdir ve boş olmalıdır; kullanımdaysa üç dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# Iki sunucuyu baslatir, kenar onbelleginin dort durumunu olcer, sonra durdurur.
node kaynak-sunucu.mjs > /dev/null &
kaynak=$!
node kenar-onbellegi.mjs > /dev/null &
kenar=$!
sleep 1

K=http://127.0.0.1:8181
OLC() { printf '%-36s' "$1"; shift;
  curl -sS -o /dev/null -w 'ilk bayt %{time_starttransfer} s  onbellek %header{x-onbellek}\n' "$@"; }

OLC "1) degismez varlik, ilk istek"   "$K/varlik/istasyon-7e1d6934.css"
OLC "2) degismez varlik, ikinci istek" "$K/varlik/istasyon-7e1d6934.css"
OLC "3) belge, ilk istek"             "$K/"
OLC "4) belge, icerik degismeden"     "$K/"

curl -sS -o /dev/null -X POST http://127.0.0.1:8180/yonetim/yayin
OLC "5) belge, yeni yayindan sonra"   "$K/"

printf '%-36s' "6) kenardaki kopya silinir"
curl -sS -X POST "$K/yonetim/gecersiz-kil?yol=/varlik/istasyon-7e1d6934.css"
OLC "7) degismez varlik, silmeden sonra" "$K/varlik/istasyon-7e1d6934.css"

echo "--- yol cozumu ---"
curl -sS -o /dev/null -w 'bilinmeyen sayfa yolu : %{http_code} %{content_type}\n' \
  "$K/olcum/kuzey-yamac/2026-03"
curl -sS -o /dev/null -w 'bulunmayan karmali ad : %{http_code}\n' \
  "$K/varlik/istasyon-00000000.css"
echo "--- belgenin son hali ---"
curl -sS "$K/"

kill "$kaynak" "$kenar"
```

```
1) degismez varlik, ilk istek       ilk bayt 0.100191 s  onbellek iska
2) degismez varlik, ikinci istek    ilk bayt 0.000624 s  onbellek isabet
3) belge, ilk istek                 ilk bayt 0.083963 s  onbellek iska
4) belge, icerik degismeden         ilk bayt 0.084585 s  onbellek dogrulandi
5) belge, yeni yayindan sonra       ilk bayt 0.085991 s  onbellek yenilendi
6) kenardaki kopya silinir          silindi : /varlik/istasyon-7e1d6934.css
7) degismez varlik, silmeden sonra  ilk bayt 0.084387 s  onbellek iska
--- yol cozumu ---
bilinmeyen sayfa yolu : 200 text/html; charset=utf-8
bulunmayan karmali ad : 404
--- belgenin son hali ---
<!doctype html><html lang="tr"><head><link rel="stylesheet" href="/varlik/istasyon-7e1d6934.css"></head><body><h1>Kuzey Yamac Olcum Istasyonu</h1><p>yayin 2</p></body></html>
```

Süre alanları makineye ve yüke bağlıdır; yorumu oranlar taşır. İlk iki satır arasındaki fark
iki mertebedir — ıskada kaynağa gidilmiş, isabette gidilmemiştir. Geri kalan bütün satırlar
ıskayla aynı mertebededir, çünkü hepsinde kaynağa bir tur yapılmıştır.

Bu, dersin ilk ölçülen sonucudur: **doğrulama da bir turdur.** Üçüncü ve dördüncü satırlar
arasındaki tek fark, dördüncüde gövdenin inmemiş olmasıdır; süre neredeyse aynıdır. Küçük
dosyalarda süreyi belirleyen şey gövdenin boyutu değil, gidip gelme sayısıdır.

Dört durum şöyle ayrılır. **Iska**, kopyanın hiç bulunmamasıdır. **İsabet**, kopyanın taze
olmasıdır; kaynağa gidilmez. **Doğrulandı**, tazeliğin bitmesi ama içeriğin değişmemiş olmasıdır;
kaynak gövdesiz bir yanıt verir ve kenardaki kopya yeniden tazelenir. **Yenilendi**, tazeliğin
bitmesi ve içeriğin de değişmiş olmasıdır; yeni gövde iner ve kopyanın yerini alır.

#### Politikanın İsabet Oranına Çevrimi

Yukarıdaki dört durumun hangi sıklıkla oluşacağını belirleyen şey, Önbellek Kırma dersinde dosya
sınıflarına göre verilen politikadır. Aşağıdaki hesap üç politikayı aynı trafik altında
karşılaştırır.

```js
// isabet.mjs — ada gore onbellek politikasinin kenar isabet oranina cevrimi.
// Model ustten sinirdir: bir tazelik penceresi icinde en cok bir kaynak istegi sayilir.
const GORUNTULEME = 240_000; // gunluk sayfa goruntulemesi
const VARLIK = 12;           // sayfa basina varlik istegi
const DUGUM = 40;            // kenar dugumu sayisi
const YAYIN = 6;             // gunluk yayin sayisi
const DEGISEN = 3;           // her yayinda icerigi degisen varlik sayisi
const GUN = 86_400;          // s
const BELGE_BAYT = 14_000;
const VARLIK_BAYT = 26_000;

const dugumBasi = GORUNTULEME / DUGUM;          // dugum basina belge istegi
const pencere = (sure) => Math.min(dugumBasi, Math.floor(GUN / sure));

// Her politika, iki dosya sinifi icin [kaynak istegi, govdeli yanit] uretir.
const POLITIKA = {
  "karmasiz ad, her sinifa 600 s": {
    belge: [pencere(600), 1 + YAYIN],
    varlik: [VARLIK * pencere(600), VARLIK + DEGISEN * YAYIN],
    bayatlik: "en cok 600 s",
  },
  "degismez varlik + dogrulanan belge": {
    belge: [dugumBasi, 1 + YAYIN],
    varlik: [VARLIK + DEGISEN * YAYIN, VARLIK + DEGISEN * YAYIN],
    bayatlik: "yok",
  },
  "degismez varlik + 60 s pencereli belge": {
    belge: [pencere(60) + YAYIN, 1 + YAYIN],
    varlik: [VARLIK + DEGISEN * YAYIN, VARLIK + DEGISEN * YAYIN],
    bayatlik: "en cok 60 s",
  },
};

const toplamIstek = GORUNTULEME * (1 + VARLIK);
const toplamBayt = GORUNTULEME * (BELGE_BAYT + VARLIK * VARLIK_BAYT);
const yuzde = (x) => (100 * x).toFixed(2) + "%";

console.log("gunluk goruntuleme " + GORUNTULEME.toLocaleString("tr-TR") +
  ", sayfa basina " + (1 + VARLIK) + " istek, " + DUGUM + " kenar dugumu, " +
  YAYIN + " yayin");
console.log("toplam istek " + toplamIstek.toLocaleString("tr-TR") +
  ", toplam gonderilen " + (toplamBayt / 1e9).toFixed(1) + " GB\n");

console.log("politika".padEnd(40) + "kaynak istegi".padStart(14) +
  "istek isabeti".padStart(15) + "bayt isabeti".padStart(14) +
  "goruntuleme basina tur".padStart(24) + "  bayatlik");

for (const [ad, p] of Object.entries(POLITIKA)) {
  const istek = DUGUM * (p.belge[0] + p.varlik[0]);
  const bayt = DUGUM * (p.belge[1] * BELGE_BAYT + p.varlik[1] * VARLIK_BAYT);
  console.log(ad.padEnd(40) + istek.toLocaleString("tr-TR").padStart(14) +
    yuzde(1 - istek / toplamIstek).padStart(15) +
    yuzde(1 - bayt / toplamBayt).padStart(14) +
    (istek / GORUNTULEME).toFixed(3).padStart(24) + "  " + p.bayatlik);
}

// Kaynaga giden her istek, Kenar Olusturma dersindeki mesafe hesabinin turunu odetir.
const TUR = 90; // ms, 9000 km icin gidis-donus
console.log("\ngoruntuleme basina kaynaga odenen sure (" + TUR + " ms tur):");
for (const [ad, p] of Object.entries(POLITIKA)) {
  const istek = DUGUM * (p.belge[0] + p.varlik[0]);
  console.log("  " + ad.padEnd(40) + ((istek / GORUNTULEME) * TUR).toFixed(1) + " ms");
}
```

```
$ node isabet.mjs
gunluk goruntuleme 240.000, sayfa basina 13 istek, 40 kenar dugumu, 6 yayin
toplam istek 3.120.000, toplam gonderilen 78.2 GB

politika                                 kaynak istegi  istek isabeti  bayt isabeti  goruntuleme basina tur  bayatlik
karmasiz ad, her sinifa 600 s                   74.880         97.60%        99.96%                   0.312  en cok 600 s
degismez varlik + dogrulanan belge             241.200         92.27%        99.96%                   1.005  yok
degismez varlik + 60 s pencereli belge          59.040         98.11%        99.96%                   0.246  en cok 60 s

goruntuleme basina kaynaga odenen sure (90 ms tur):
  karmasiz ad, her sinifa 600 s           28.1 ms
  degismez varlik + dogrulanan belge      90.4 ms
  degismez varlik + 60 s pencereli belge  22.1 ms
```

Tablodaki en öğretici sütun, bütün politikalarda aynı çıkan sütundur: **bayt isabet oranı üç
politikada da aynıdır.** Kaynaktan inen gövde miktarını belirleyen şey politika değil,
içeriğin gerçekten kaç kez değiştiğidir. Bir varlık günde altı kez değişiyorsa her kenar
düğümü onu en çok altı kez indirir.

Politikanın değiştirdiği şey **istek sayısıdır**. İkinci satırda, her kullanımda doğrulanan
belge yüzünden görüntüleme başına bir tam tur kaynağa ödenir — dokuz bin kilometre için
yaklaşık 90 milisaniye, üstelik hiçbir bayt inmeden. Bu, üçüncü sütunun neden yanıltıcı bir
başarı ölçüsü olduğunu gösterir: bir yayın yüzde 99,96 bayt isabetiyle çalışırken kullanıcıya
her sayfada bir kıtalar arası tur ödetiyor olabilir.

Üçüncü satır ödünleşimi bir ölçüye bağlar. Belgeye kısa da olsa bir tazelik penceresi
vermek, görüntüleme başına ödenen turu dörtte bire indirir; karşılığı, yayından sonra en çok
altmış saniye eski belgenin sunulmasıdır. Artımlı Yeniden Üretim dersindeki pencere hesabının
aynısıdır; orada tazelik ile kaynak yükü, burada tazelik ile gecikme takas edilir.

Birinci satır karmalı adlandırmanın kazancını gösterir: karmasız adlarda değişmemiş varlıklar da
pencere dolunca yeniden doğrulanır (varlık başına günde yüz kırk dört tur), değiştirilemez
işaretiyle bu turlar tümüyle kalkar.

#### Geçersiz Kılma ve Yayın Anı

Yayın yapıldığında kenar düğümlerindeki kopyalar hâlâ eskidir. İki yol vardır ve dosya
sınıfına göre ayrılırlar.

**Değiştirilemez adlı varlıklar için hiçbir şey yapılmaz.** Yeni yayının varlıkları yeni adlar
taşır; o adlar hiçbir kenarda bulunmadığı için ilk istekte ıska verir. Önbellek Kırma dersindeki
eklemeli yayın kuralı kenar kopyaları için de geçerlidir.

**Belge için geçersiz kılma gerekir.** Belgenin adresi değişmediğinden, kenardaki kopyanın
elle silinmesi ya da bir doğrulamaya zorlanması gerekir. Ölçümün altıncı ve yedinci satırları
bu işlemi gösterir: silinen kopya bir sonraki istekte ıska verir.

Geçersiz kılmanın iki özelliği kararı etkiler. Birincisi, **anında değildir**: düğümler
arasında yayılması zaman alır ve bu süre boyunca farklı kullanıcılar farklı sürümleri görür.
İkincisi, **tek başına yeterli değildir**: kullanıcının kendi tarayıcı önbelleğindeki kopya
kenardan silinemez. Belgeye uzun ömür verilmemesinin nedeni budur.

#### Yol Çözümü ve Yedek Belge

Statik barındırmada yolun karşılığı bir dosyadır. Adresler derleme zamanında biliniyorsa
her adres için bir dosya üretilir ve eşleme doğrudandır. Adres istasyon kimliği ya da tarih
aralığı taşıyorsa ve bu değerler derleme zamanında bilinmiyorsa dosya üretilemez.

Bu durumda kullanılan çözüm **yedek belgedir**: bilinen bir dosyaya eşlenmeyen her yol,
uygulamanın belgesine çözülür ve yolu istemcideki yönlendirme yorumlar. Ölçümün yol çözümü
bölümü bunu gösterir: bilinmeyen bir sayfa yolu belgeyi ve `200` durum kodunu alır.

Yedek belgenin iki kuralı vardır.

**Varlık yolları yedeğe düşmemelidir.** Bulunmayan bir karmalı ad `404` almalıdır. Yedeğe
düşerse tarayıcı, betik beklediği yerde HTML alır; hata ayrıştırma hatası olarak, gerçek
nedeninden uzakta görünür. Ölçümün son satırı bu ayrımı doğrular.

**Var olmayan sayfa gerçekten `404` dönmelidir.** Her yolun `200` ile belgeye çözülmesi,
olmayan bir kaynağın var gibi bildirilmesidir; dizinleyiciler bu yanıtı doğru kabul eder.
Bilinen sayfa yollarının listesi derleme zamanında üretilebiliyorsa, liste dışındaki yollar
gerçek bir hata yanıtı alır.

#### Bu Modelin Sınırı

Statik barındırmanın gücü yanıtın istekten bağımsız olmasından gelir; sınırı da aynı yerden.

Kişiye özel içerik üretilemez: yetkiye bağlı bölümler, oturuma bağlı görünümler ve isteğin
başlıklarına bakarak verilen kararlar bu modelde yoktur. `Vary` başlığıyla yanıtı isteğin bir
boyutuna göre çeşitlendirmek olanaklıdır, ama her çeşit önbellekte ayrı bir kopyadır; çeşit
sayısı arttıkça isabet oranı düşer ve modelin kazancı erir. Gizli değer gerektiren işler de
dışarıda kalır, çünkü sunulan her bayt açıktır.

Bu iki sınır, istek anında çalışan bir kod parçasını gerektirir.

#### Özet

- Statik barındırmada yayın bir dosya kopyasıdır; istek başına maliyet hemen hemen sıfırdır,
  ölçekleme kopya sayısını artırmakla yapılır, yanıt istekten bağımsızdır.
- Kenar önbelleğinin dört durumu ölçülebilir: ıska, isabet, doğrulandı ve yenilendi. İsabet
  dışındaki üçünde kaynağa bir tur yapılır; gövdesiz doğrulama da tam bir turdur.
- Bayt isabet oranını politika değil içeriğin değişme sıklığı belirler; üç ayrı politika aynı
  bayt oranını verir. Politikanın belirlediği şey istek sayısıdır.
- Her kullanımda doğrulanan belge, görüntüleme başına bir tam tur ödetir; belgeye altmış
  saniyelik bir pencere vermek bu turu dörtte bire indirir, karşılığı sınırlı bayatlıktır.
- Değiştirilemez adlı varlıklar geçersiz kılma gerektirmez; belge gerektirir ve geçersiz kılma ne
  anında yayılır ne de kullanıcının kendi önbelleğine ulaşır.
- Bilinmeyen yollar yedek belgeye çözülür, ama varlık yolları `404` dönmeli ve var olmayan
  sayfalar `200` ile bildirilmemelidir.

#### Sonraki Adım

Statik barındırmanın dışarıda bıraktığı her iş — oturuma bakan, gizli değer kullanan, isteğin
kendisine göre karar veren iş — istek anında çalışan bir koda ihtiyaç duyar. Kuzey Yamaç
sitesinde bu, arşiv yazma ucu, oturum yenileme ve yetkiye bağlı düzenleme görünümleridir.
Sonraki ders bu kodun nerede çalışacağını sorar: sürekli ayakta duran bir süreçte mi, istek
başına başlatılan bir örnekte mi, yoksa kenardaki dar bir çalışma zamanında mı. Üç seçenek
soğuk başlatma, durum tutabilme, eşzamanlılık ve kapanma davranışı eksenlerinde ayrılır ve
bu eksenlerin hepsi ölçülebilir.

### Sunucu Gerektiren Dağıtım

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/sunucu-gerektiren-dagitim

İstek anında çalışan kodun barındırılma biçimleri; uzun ömürlü süreç, istek başına örnek ve kenar çalışma zamanının soğuk başlatma, gecikme, ayrılmış kapasite ve durum tutma eksenlerinde ölçülmesi, hazırlık ucu ile zarif kapanma sırası.

Statik barındırmanın dışarıda bıraktığı her iş, istek anında çalışan bir koda ihtiyaç duyar.
Kuzey Yamaç sitesinde bunlar arşiv yazma ucu, oturum yenileme ve yetkiye bağlı düzenleme
görünümleridir; ortak özellikleri gizli değer kullanmaları ve yanıtı isteğe göre
üretmeleridir.

Bu ders o kodun nerede çalışacağını sorar. Üç seçenek vardır ve aralarındaki fark bir tercih
meselesi değil, ölçülebilir bir davranış farkıdır.

#### Üç Barındırma Biçimi

**Uzun ömürlü süreç.** Belirli sayıda örnek sürekli ayakta durur ve gelen istekleri
aralarında paylaşır. Her örnek eşzamanlı olarak çok sayıda isteği yürütebilir; toplam
kapasite, örnek sayısı ile örnek başına eşzamanlılık sınırının çarpımıdır. Bu kapasite
isteğin gelip gelmemesinden bağımsız olarak ayrılmıştır. Süreç ayakta kaldığı için bellekte
durum tutabilir, veri deposuna bir bağlantı havuzu açık tutabilir.

**İstek başına örnek.** İstek geldiğinde bir örnek ayağa kaldırılır, isteği işler, ardından
bir süre boşta bekler ve sönme sırasına girer. Örnek başına eşzamanlılık genellikle birdir;
eşzamanlı iki istek iki örnek demektir. Ayakta örnek yoksa **soğuk başlatma (cold start)**
ödenir — Kenar Oluşturma dersinde başlatma süresi olarak anılan kalem budur. Ayrılmış
kapasite talebi izler, ama boşta bekleme süresi kadar gecikmeyle.

**Kenar çalışma zamanı.** Aynı örnek modeli, çok sayıda düğüme dağıtılmış hâlidir. Soğuk
başlatma çok daha kısadır, çünkü çalıştırılan kodun bütçesi dardır. Karşılığında veri
kaynağı uzaktadır: Kenar Oluşturma dersinin kuralı gereği, veri kenara taşınamadığı ölçüde
her istek o mesafeyi öder.

#### Aynı Yükü Üç Biçimde Yürütmek

Üç biçim aynı yük izi üzerinde ölçülebilir. Aşağıdaki benzetim sanal saat kullanır; gerçek
zaman okunmadığı için çıktı her koşuda aynıdır.

```js
// calisma-zamani.mjs — ayni yuku uc calisma zamani biciminde yurutur.
// Sanal saat kullanilir; Date.now() cagrilmaz, sonuc her kosuda aynidir.

// Belirlenimci uretec (splitmix32). Tohum sabit oldugu icin varis izi degismez.
function uretec(tohum) {
  let s = tohum >>> 0;
  return () => {
    s = (s + 0x9e3779b9) >>> 0;
    let z = s;
    z = Math.imul(z ^ (z >>> 16), 0x21f0aaad) >>> 0;
    z = Math.imul(z ^ (z >>> 15), 0x735a2d97) >>> 0;
    return ((z ^ (z >>> 15)) >>> 0) / 4294967296;
  };
}

const SURE = 600_000; // ms
const HIZMET = 120;   // ms, isin kendisi: veri okuma + sablon
const PENCERE = [
  ["seyrek", 0, 200_000, 0.5],
  ["duzenli", 200_000, 400_000, 60],
  ["patlama", 400_000, 410_000, 600],
  ["toparlanma", 410_000, 600_000, 60],
];

const hizi = (t) => PENCERE.find(([, a, b]) => t >= a && t < b)[3];

// Varis izi: usel araliklar, pencereye gore degisen hizla.
function varislar() {
  const r = uretec(20_260_327);
  const liste = [];
  let t = 0;
  while (t < SURE) {
    t += (-Math.log(1 - r()) / hizi(t)) * 1000;
    if (t < SURE) liste.push(t);
  }
  return liste;
}

// 1) Uzun omurlu surec: sabit sayida yuva hep ayrilidir, soguk baslatma yoktur,
//    yuvalar dolduysa istek kuyrukta bekler.
function uzunOmurlu(varis, { yuva, hizmet }) {
  const bos = new Array(yuva).fill(0);
  const kayit = [];
  for (const t of varis) {
    let k = 0;
    for (let i = 1; i < yuva; i++) if (bos[i] < bos[k]) k = i;
    const baslangic = Math.max(t, bos[k]);
    kayit.push({ t, gecikme: baslangic - t + hizmet, soguk: false });
    bos[k] = baslangic + hizmet;
  }
  return { kayit, araliklar: new Array(yuva).fill([0, SURE]) };
}

// 2) Istek basina ornek: bir ornek tek istek isler, isi bitince bostaOmur kadar
//    sicak bekler, sonra sonar. Sicak ornek yoksa soguk baslatma odenir.
function istekBasina(varis, { hizmet, soguk, bostaOmur }) {
  const kayit = [], araliklar = [];
  let canli = [];
  for (const t of varis) {
    canli = canli.filter((o) => {
      if (o.omurSonu > t) return true;
      araliklar.push([o.olusma, o.omurSonu]);
      return false;
    });
    const sicak = canli.find((o) => o.mesgulSonu <= t);
    if (sicak) {
      kayit.push({ t, gecikme: hizmet, soguk: false });
      sicak.mesgulSonu = t + hizmet;
      sicak.omurSonu = sicak.mesgulSonu + bostaOmur;
    } else {
      kayit.push({ t, gecikme: soguk + hizmet, soguk: true });
      const mesgulSonu = t + soguk + hizmet;
      canli.push({ mesgulSonu, omurSonu: mesgulSonu + bostaOmur, olusma: t });
    }
  }
  for (const o of canli) araliklar.push([o.olusma, Math.min(o.omurSonu, SURE)]);
  return { kayit, araliklar };
}

// 3) Kenar calisma zamani: ayni ornek modeli, yuk dugumlere bolunur, veri uzaktadir.
function kenarCalismaZamani(varis, { dugum, ...ayar }) {
  const paylar = Array.from({ length: dugum }, () => []);
  varis.forEach((t, i) => paylar[i % dugum].push(t));
  const kayit = [], araliklar = [];
  for (const pay of paylar) {
    const s = istekBasina(pay, ayar);
    kayit.push(...s.kayit);
    araliklar.push(...s.araliklar);
  }
  return { kayit, araliklar };
}

const yuzdelik = (dizi, p) => {
  const s = [...dizi].sort((a, b) => a - b);
  return s.length === 0 ? 0 : s[Math.min(s.length - 1, Math.floor((p / 100) * s.length))];
};
const ortusme = (aralik, a, b) =>
  Math.max(0, Math.min(aralik[1], b) - Math.max(aralik[0], a));

function ozet({ kayit, araliklar }, a, b) {
  const dilim = kayit.filter((k) => k.t >= a && k.t < b);
  const ayrilmis = araliklar.reduce((s, ar) => s + ortusme(ar, a, b), 0) / 1000;
  const kullanilan = (dilim.length * HIZMET) / 1000;
  return {
    istek: dilim.length,
    p50: yuzdelik(dilim.map((k) => k.gecikme), 50),
    p95: yuzdelik(dilim.map((k) => k.gecikme), 95),
    p99: yuzdelik(dilim.map((k) => k.gecikme), 99),
    soguk: (100 * dilim.filter((k) => k.soguk).length) / dilim.length,
    ayrilmis,
    kullanim: (100 * kullanilan) / ayrilmis,
  };
}

const varis = varislar();
console.log("varis izi: " + varis.length.toLocaleString("tr-TR") + " istek, " +
  SURE / 1000 + " s, isin kendisi " + HIZMET + " ms");
for (const [ad, a, b, hiz] of PENCERE)
  console.log("  " + ad.padEnd(12) + (a / 1000) + "-" + (b / 1000) + " s   " + hiz + " istek/s");

const MODEL = [
  ["uzun omurlu surec (75 yuva)", uzunOmurlu(varis, { yuva: 75, hizmet: HIZMET })],
  ["istek basina ornek", istekBasina(varis, { hizmet: HIZMET, soguk: 350, bostaOmur: 60_000 })],
  ["kenar calisma zamani (40 dugum)",
   kenarCalismaZamani(varis, { dugum: 40, hizmet: HIZMET + 60, soguk: 40, bostaOmur: 5_000 })],
];

console.log("\n" + "pencere".padEnd(12) + "model".padEnd(33) + "istek".padStart(7) +
  "p50".padStart(6) + "p95".padStart(6) + "p99".padStart(6) + "soguk".padStart(8) +
  "ayrilmis ornek-s".padStart(18) + "kullanim".padStart(10));
for (const [ad, a, b] of PENCERE) {
  for (const [mad, s] of MODEL) {
    const o = ozet(s, a, b);
    console.log(ad.padEnd(12) + mad.padEnd(33) + String(o.istek).padStart(7) +
      o.p50.toFixed(0).padStart(6) + o.p95.toFixed(0).padStart(6) +
      o.p99.toFixed(0).padStart(6) + o.soguk.toFixed(1).padStart(7) + "%" +
      o.ayrilmis.toFixed(0).padStart(18) + o.kullanim.toFixed(1).padStart(9) + "%");
  }
}
```

```
$ node calisma-zamani.mjs
varis izi: 29.655 istek, 600 s, isin kendisi 120 ms
  seyrek      0-200 s   0.5 istek/s
  duzenli     200-400 s   60 istek/s
  patlama     400-410 s   600 istek/s
  toparlanma  410-600 s   60 istek/s

pencere     model                              istek   p50   p95   p99   soguk  ayrilmis ornek-s  kullanim
seyrek      uzun omurlu surec (75 yuva)           87   120   120   120    0.0%             15000      0.1%
seyrek      istek basina ornek                    87   120   120   470    3.4%               354      2.9%
seyrek      kenar calisma zamani (40 dugum)       87   220   220   220  100.0%               446      2.3%
duzenli     uzun omurlu surec (75 yuva)        12010   120   120   120    0.0%             15000      9.6%
duzenli     istek basina ornek                 12010   120   120   120    0.2%              4133     34.9%
duzenli     kenar calisma zamani (40 dugum)    12010   180   180   180    0.3%              7968     18.1%
patlama     uzun omurlu surec (75 yuva)         5947   123   208   235    0.0%               750     95.2%
patlama     istek basina ornek                  5947   120   120   470    3.4%              2159     33.0%
patlama     kenar calisma zamani (40 dugum)     5947   180   180   220    2.0%              1488     48.0%
toparlanma  uzun omurlu surec (75 yuva)        11611   120   120   120    0.0%             14250      9.8%
toparlanma  istek basina ornek                 11611   120   120   120    0.0%             14601      9.5%
toparlanma  kenar calisma zamani (40 dugum)    11611   180   180   180    0.0%              8197     17.0%
```

Tablo, hiçbir biçimin bütün pencerelerde üstün olmadığını gösterir.

**Seyrek pencere ayrılmış kapasiteyi cezalandırır.** Seksen yedi istek için uzun ömürlü
süreç on beş bin örnek-saniye ayırır ve bunun binde birini kullanır. İstek başına örnek aynı
yükü üç yüz elli dört örnek-saniyeyle karşılar — kırk kat az. Kenar çalışma zamanı bu pencerede
her isteği soğuk başlatır, ama ödediği kalem kırk milisaniyedir.

**Patlama penceresi ayrılmış kapasiteyi haklı çıkarır.** Uzun ömürlü sürecin kullanımı yüzde
95'e çıkar; kapasite tam kullanılmıştır. Ama sınıra dayanmanın bedeli kuyruktur: p50 123
milisaniyede kalırken p95 208, p99 235 milisaniyeye çıkar. Bu artış işin uzamasından değil,
isteğin sırasını beklemesinden gelir. İstek başına örnek aynı patlamada p50'yi hiç
bozmaz — kuyruk yoktur, örnek sayısı talebi izler.

**Soğuk başlatma orta değerlerde görünmez.** Patlama penceresinde istek başına örnek
modelinin soğuk başlatma oranı yüzde 3,4'tür; p50 ve p95 bunu hiç göstermez, p99 470
milisaniye olarak gösterir. Ölçüyü ortalamadan okuyan biri soğuk başlatmayı hiç fark etmez.
Kuyruk ve soğuk başlatma, ancak üst yüzdeliklerde ayırt edilebilir.

**Toparlanma penceresi boşta bekleme süresinin bedelini gösterir.** Patlama bittikten sonra
istek başına örnek modelinin ayrılmış kapasitesi on dört bin altı yüz örnek-saniyeye çıkar
ve kullanımı yüzde 9,5'e düşer — uzun ömürlü sürecin yüzde 9,8'inin altına. Neden, patlamada
açılan yüzlerce örneğin boşta bekleme süresi boyunca ayakta kalmasıdır. Talebi izleyen kapasite,
talebi bir gecikmeyle izler.

**Kenar çalışma zamanının bedeli her satırda ödenir.** p50 değeri yüklü pencerelerde 180,
seyrek pencerede 220 milisaniyedir; ilk sayı veri deposuna olan altmış milisaniyelik
mesafenin her istekte ödenmesinden, ikincisindeki kırk milisaniyelik fark ise seyrek yükte
her isteğin soğuk başlatmaya düşmesinden gelir. Kenarın yakınlıktan gelen kazancı bu tabloda
yoktur; tablo yalnız sunucu tarafındaki davranışı ölçer. Kalemlerin toplamı,
Kenar Oluşturma dersindeki kuralı tekrarlar: kenar, verisi de kenara taşınabildiği ölçüde
kazandırır.

#### Durumun Nerede Durduğu

Bellekte tutulan durum yalnız uzun ömürlü süreçte anlamlıdır ve orada da koşullu olarak
anlamlıdır. Örnek sayısı birden büyükse iki istek iki ayrı örneğe düşebilir; bir örnekte
tutulan oturum, sayaç ya da önbellek ötekinde yoktur.

Bunun üç sonucu vardır. **Oturum durumu paylaşılan bir depoda tutulur** ya da istemcinin
taşıdığı imzalı bir değerde durur; Uygulama Mimarisi kursundaki belirteç saklama kararı bu
noktada dağıtım kararıyla birleşir. **Oran sınırı ve kuyruk sayaçları paylaşılan depoya
taşınır**; örnek başına sayılan bir sınır, örnek sayısıyla çarpılan bir sınırdır. **Bağlantı
havuzu örnek başınadır**; veri deposunun bağlantı sınırı, örnek sayısı ile havuz boyutunun
çarpımına göre belirlenir. İstek başına örnek modelinde bu çarpım patlama sırasında hızla
büyür ve deponun sınırına ilk çarpan kalem genellikle burasıdır.

Aynı kullanıcıyı hep aynı örneğe yönlendirmek — yapışkan atama — durumu bellekte tutmayı
sürdürmenin yolu gibi görünür. Bir bedeli vardır: örnek söndüğünde o kullanıcının durumu
kaybolur ve yük dağılımı bozulur. Durumu paylaşılan depoya taşımak bu iki sorunu da doğurmaz.

#### Hazırlık Ucu ve Zarif Kapanma

Sunucu gerektiren dağıtımın statik barındırmada karşılığı olmayan bir sorunu vardır: yayın,
çalışan bir süreci durdurmayı gerektirir. Süreç durdurulurken elindeki istekler yarıda
kalırsa, kullanıcı yayının olduğunu bir hata olarak görür.

Doğru kapanma sırası dört adımdır: hazırlık ucunun olumsuza dönmesi, yönlendiricinin bunu
fark etmesi için bir pay beklenmesi, dinleyicinin kapatılması ve açık isteklerin bitmesinin
beklenmesi.

```js
// zarif-kapanma.mjs — uzun omurlu surecin kapanma sirasi.
// 1) hazirlik ucu olumsuza doner, 2) yonlendiricinin fark etmesi icin beklenir,
// 3) dinleyici kapanir, 4) acik istekler bitince surecten cikilir.
import { createServer } from "node:http";

const IS_SURESI = 1500;    // ms, arsiv yaziminin surdugu sure
const HAZIRLIK_PAYI = 600; // ms, hazirlik ucu ile dinleyici kapanisi arasi

let hazir = true;
let acikIstek = 0;

const sunucu = createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  if (istek.url === "/hazirlik") {
    yanit.writeHead(hazir ? 200 : 503, { "Content-Type": "text/plain" })
      .end(hazir ? "hazir\n" : "kapaniyor\n");
    return;
  }
  acikIstek += 1;
  await new Promise((c) => setTimeout(c, IS_SURESI));
  acikIstek -= 1;
  yanit.writeHead(200, { "Content-Type": "text/plain" }).end("arsivlendi\n");
});

sunucu.listen(8182, "127.0.0.1", () => console.log("sunucu  : dinleniyor"));

process.on("SIGTERM", () => {
  hazir = false;
  console.log("sunucu  : SIGTERM alindi, hazirlik olumsuza dondu, acik istek " + acikIstek);
  setTimeout(() => {
    sunucu.close(() => {
      console.log("sunucu  : acik istek " + acikIstek + ", surecten cikiliyor");
      process.exit(0);
    });
    console.log("sunucu  : dinleyici kapatildi, yeni baglanti alinmiyor");
  }, HAZIRLIK_PAYI);
});
```

Aşağıdaki betik bu dosyayla aynı dizinde çalıştırılır. Bağlantı noktası 8182 keyfîdir ve boş
olmalıdır; kullanımdaysa iki dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# Kapanma sirasini olcer: acik istek biter, yeni baglanti alinmaz.
node zarif-kapanma.mjs &
sunucu=$!
sleep 0.5

curl -sS -o /dev/null \
  -w 'istemci : surmekte olan istek durum %{http_code}, sure %{time_total} s\n' \
  http://127.0.0.1:8182/arsiv &
yavas=$!
sleep 0.3

curl -sS -o /dev/null -w 'istemci : hazirlik ucu %{http_code} (SIGTERM oncesi)\n' \
  http://127.0.0.1:8182/hazirlik
kill -TERM "$sunucu"
sleep 0.3
curl -sS -o /dev/null -w 'istemci : hazirlik ucu %{http_code} (SIGTERM sonrasi)\n' \
  http://127.0.0.1:8182/hazirlik

sleep 0.6
if curl -sS -o /dev/null http://127.0.0.1:8182/arsiv 2> /dev/null; then
  echo "istemci : yeni istek kabul edildi"
else
  echo "istemci : yeni istek baglanamadi, curl cikis kodu $?"
fi

wait "$yavas"
wait "$sunucu"
```

```
sunucu  : dinleniyor
istemci : hazirlik ucu 200 (SIGTERM oncesi)
sunucu  : SIGTERM alindi, hazirlik olumsuza dondu, acik istek 1
istemci : hazirlik ucu 503 (SIGTERM sonrasi)
sunucu  : dinleyici kapatildi, yeni baglanti alinmiyor
istemci : yeni istek baglanamadi, curl cikis kodu 7
istemci : surmekte olan istek durum 200, sure 1.504674 s
sunucu  : acik istek 0, surecten cikiliyor
```

Sürelerin tamamı ortama bağlıdır; belirleyici olan sıradır. Kapanma sinyali geldiğinde
elde bir açık istek vardır; o istek `200` ile ve tam süresinde tamamlanır. Sinyalden sonra
hazırlık ucu `503` döner ama bağlantı noktası hâlâ açıktır — bu pay, yönlendiricinin bu
örneği listeden çıkarması için bırakılmıştır. Pay dolduğunda dinleyici kapanır ve yeni
bağlantı kurulamaz; süreçten çıkış ise açık isteğin bitmesini bekler.

Payın atlanması iki yanlıştan birine yol açar. Dinleyici hemen kapatılırsa, yönlendirici
henüz haberi olmadığı için bu örneğe istek göndermeye devam eder ve o istekler bağlantı
hatası alır. Hazırlık ucu hiç kullanılmazsa, aynı sonuç kapanmanın ilk milisaniyesinden
itibaren oluşur. İstek başına örnek ve kenar biçimlerinde bu sıra platformun sorumluluğundadır,
ama açık isteğin yarıda kesilmemesi için işin süresi platformun tanıdığı süreden kısa
olmalıdır.

#### Seçim Kuralı

Karar üç ölçüye bağlanır: yükün düzenliliği, işin süresi ve verinin konumu.

Yük düzenliyse ve boşta kalan pencere yoksa uzun ömürlü süreç seçilir; ayrılmış kapasitenin
kullanımı yüksektir, soğuk başlatma yoktur ve bağlantı havuzu ile bellek içi önbellek kullanılır.
Yük seyrek ya da ani patlamalıysa istek başına örnek seçilir; ayrılmış kapasite talebi izler,
karşılığı üst yüzdeliklerde görünen soğuk başlatmadır. İş kısa, veriye bağımlılığı az ve coğrafi
yakınlık belirleyiciyse kenar çalışma zamanı seçilir.

Bu kural tek bir seçim yapılacağı anlamına gelmez. Kuzey Yamaç sitesinde
statik barındırma sayfaları, uzun ömürlü bir süreç arşiv yazma ucunu, kenar çalışma zamanı da
yönlendirme ve başlık ekleme işlerini üstlenebilir. Ayrım işin kendisine göre yapılır.

#### Özet

- Üç barındırma biçimi ayrılmış kapasite, soğuk başlatma, eşzamanlılık ve durum tutma
  eksenlerinde ayrılır; hiçbiri bütün yük pencerelerinde üstün değildir.
- Seyrek yükte uzun ömürlü süreç ayırdığı kapasitenin binde birini kullanır; istek başına
  örnek aynı yükü kırk kat az kapasiteyle karşılar.
- Patlamada uzun ömürlü süreç kapasitesini tam kullanır ama kuyruk oluşur: p50 sabit
  kalırken p99 yükselir. İstek başına örnek kuyruk üretmez, soğuk başlatma üretir.
- Soğuk başlatma ve kuyruk orta değerlerde görünmez; ölçü üst yüzdeliklerden okunur.
- Bellekte durum yalnız tek örnekte güvenlidir; oturum, sayaç ve önbellek paylaşılan depoya
  taşınır, bağlantı havuzu ise örnek sayısıyla çarpılır.
- Zarif kapanma dört adımdır: hazırlık ucu olumsuza döner, pay beklenir, dinleyici kapanır,
  açık istekler biter. Pay atlanırsa yönlendirici hâlâ istek gönderir ve o istekler düşer.

#### Sonraki Adım

Buraya kadar iki dağıtım biçimi ve bir kapanma sırası kuruldu; hepsi tek bir hedefe, yayına
bakıyordu. Oysa bir değişikliğin yayına girmeden önce çalıştığının görülmesi gerekir ve Geliştirme
Sunucusu dersi bunun nedenini vermişti: geliştirme kipinde çalışması, yayın kipinde çalıştığının
kanıtı değildir. Sonraki ders her değişiklik için ayrı bir ortam kurar: kendi adresi olan, yayın
kipiyle derlenmiş, yayın verisine dokunmayan ve dizinlenmeyen bir dağıtım. Bu ortamların kimliği,
Önbellek Kırma dersindeki belirlenimci adlandırmadan türetilir.

### Önizleme Dağıtımları

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/onizleme-dagitimlari

Her değişiklik için ayrı bir ortam üretmek; içerikten türetilen belirlenimci dağıtım kimliği, blokları paylaşan içerik adresli depo, önizlemelerin yayınla yan yana sunulması, veri ve yan etki yalıtımı ile dağıtımların ömrü.

Önceki iki ders yayına bakıyordu: çıktının nasıl sunulduğuna ve istek anında çalışan kodun nerede
durduğuna. Bir değişikliğin yayına girmeden önce çalıştığının görülmesi ise ayrı bir sorundur ve
Geliştirme Sunucusu dersi nedenini vermişti — geliştirme kipinde çalışması, yayın kipinde
çalıştığının kanıtı değildir.

Bu ders her değişiklik için ayrı bir ortam kurar: yayın kipiyle derlenmiş, kendi adresi olan,
yayına dokunmayan ve dizinlenmeyen bir dağıtım.

#### Önizleme Dağıtımının Karşılaması Gerekenler

Bir önizleme dağıtımı altı koşulu birden sağladığında işe yarar.

**Yayınla aynı hat.** Aynı derleme adımları, aynı kip, aynı denetimler. Farklı bir hatla
üretilen bir önizleme, kanıtlamak istediği şeyi kanıtlamaz.

**Kararlı ve türetilmiş adres.** Dağıtımın kimliği içeriğinden türetilir; aynı değişiklik
her zaman aynı kimliği verir. Önbellek Kırma dersindeki belirlenimcilik koşulu burada bir
kimliğe dönüşür.

**Yayınla aynı anda yaşayabilme.** Önizlemenin varlığı yayının davranışını hiçbir biçimde
değiştirmez; ikisi aynı sunucudan aynı anda sunulabilir.

**Veri ve yan etki yalıtımı.** Önizleme yayın verisine yazmaz, dışa giden yan etkileri
tetiklemez.

**Dizinlenmeme ve erişim denetimi.** Önizleme adresleri arama dizinlerine girmemeli, herkese
açık olmamalıdır.

**Sonlu ömür.** Değişiklik kapandığında dağıtım kaldırılır.

#### Dağıtım Kimliği ve Paylaşılan Depo

Aşağıdaki betik üç dalı — yayın ve iki değişiklik — kendi ortamlarıyla derler, dosyaları
içerik adresli bir depoya yazar ve her dağıtım için bir kimlik üretir.

```js
// onizleme.mjs — degisiklik basina dagitim: icerik adresli kimlik ve paylasilan depo.
import { createHash } from "node:crypto";
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
import { join } from "node:path";

const karma = (g) => createHash("sha256").update(g).digest("hex");

// Yayindaki kaynak agac. {{AD}} yer tutuculari Ortam Yapilandirmasi dersindeki ikameyle doldurulur.
const TABAN = {
  "belge.html":
    '<!doctype html><html lang="tr"><body>' +
    "<h1>Kuzey Yamac Olcum Istasyonu</h1>" +
    "<p>uc: {{OLCUM_UCU}}</p><p>dal: {{DAL}}</p></body></html>\n",
  "varlik/istasyon.css": ".olcum { font-variant-numeric: tabular-nums; }\n",
  "varlik/giris.js": 'import "./panel.js";\n',
  "varlik/panel.js": 'export const ciz = (o) => o.map((x) => x.sicaklik).join(" ");\n',
  "varlik/grafik.js": "export const eksen = (a, b) => [a, b];\n",
};

// Uc dal: yayin ve iki ayri degisiklik. Her dal kendi ortamiyla derlenir.
const DAL = {
  yayin: { degisiklik: {}, ortam: { OLCUM_UCU: "https://olcum.kuzey-yamac.example/v1" } },
  "esik-uyarisi": {
    degisiklik: {
      "varlik/panel.js":
        'export const ciz = (o) =>\n' +
        '  o.map((x) => (x.sicaklik < -20 ? "!" : "") + x.sicaklik).join(" ");\n',
    },
    ortam: { OLCUM_UCU: "https://deneme.olcum.kuzey-yamac.example/v1" },
  },
  "tarih-bicimi": {
    degisiklik: {
      "varlik/grafik.js": "export const eksen = (a, b) => [a, b, \"UTC\"];\n",
      "varlik/istasyon.css":
        ".olcum { font-variant-numeric: tabular-nums; font-feature-settings: \"ss01\"; }\n",
    },
    ortam: { OLCUM_UCU: "https://deneme.olcum.kuzey-yamac.example/v1" },
  },
};

const DEPO = "depo";
const sayac = { yazilan: 0, yeniden: 0 };

function derle(dalAdi) {
  const { degisiklik, ortam } = DAL[dalAdi];
  const tam = { ...ortam, DAL: dalAdi };
  const bildirim = {};
  for (const yol of Object.keys(TABAN).sort()) {
    const kaynak = degisiklik[yol] ?? TABAN[yol];
    const govde = kaynak.replace(/\{\{([A-Z_]+)\}\}/g, (t, ad) => tam[ad] ?? t);
    const k = karma(govde);
    const hedef = join(DEPO, k);
    if (existsSync(hedef)) sayac.yeniden += 1;
    else { writeFileSync(hedef, govde); sayac.yazilan += 1; }
    bildirim[yol] = k;
  }
  // Dagitim kimligi: yol-karma ciftlerinin siralanmis listesinin karmasi.
  const kimlik = karma(Object.entries(bildirim).map(([y, k]) => y + " " + k).join("\n"))
    .slice(0, 12);
  mkdirSync(join("dagitim", kimlik), { recursive: true });
  writeFileSync(join("dagitim", kimlik, "bildirim.json"),
    JSON.stringify({ dal: dalAdi, dosyalar: bildirim }, null, 2) + "\n");
  return { kimlik, bildirim };
}

rmSync(DEPO, { recursive: true, force: true });
rmSync("dagitim", { recursive: true, force: true });
mkdirSync(DEPO, { recursive: true });

const sonuc = {};
for (const dal of Object.keys(DAL)) sonuc[dal] = derle(dal);

console.log("dal".padEnd(16) + "dagitim kimligi".padEnd(18) + "dosya");
for (const [dal, s] of Object.entries(sonuc))
  console.log(dal.padEnd(16) + s.kimlik.padEnd(18) + Object.keys(s.bildirim).length);

console.log("\ndepo: " + sayac.yazilan + " ayri blok yazildi, " +
  sayac.yeniden + " blok yeniden kullanildi (toplam " +
  (sayac.yazilan + sayac.yeniden) + " dosya girisi)");

// Ayni kaynaktan ikinci derleme ayni kimligi vermelidir.
const tekrar = Object.fromEntries(Object.keys(DAL).map((d) => [d, derle(d).kimlik]));
console.log("ikinci derleme ayni kimlikleri verdi: " +
  Object.entries(sonuc).every(([d, s]) => s.kimlik === tekrar[d]));

// Yayin dagitimini isaretle ve kimlikleri sunucunun okuyacagi dosyaya yaz.
writeFileSync("guncel.txt", sonuc.yayin.kimlik + "\n");
writeFileSync("kimlikler.txt",
  Object.entries(sonuc).map(([d, s]) => d + " " + s.kimlik).join("\n") + "\n");

console.log("\ndagitimlar arasi fark (yayin ile karsilastirma):");
for (const dal of ["esik-uyarisi", "tarih-bicimi"]) {
  const fark = Object.keys(TABAN).sort()
    .filter((y) => sonuc[dal].bildirim[y] !== sonuc.yayin.bildirim[y]);
  console.log("  " + dal.padEnd(16) + fark.length + "/" + Object.keys(TABAN).length +
    " dosya farkli: " + fark.join(", "));
}
```

#### Yan Yana Sunmak

Üç dağıtım aynı sunucudan sunulur. Yayın kökte, önizlemeler kimliklerinin altında durur.

```js
// onizleme-sunucu.mjs — dagitimlari kimliklerine gore yan yana sunar.
// Kokte yayin dagitimi, /o/<kimlik>/ altinda onizleme dagitimlari durur.
import { createServer } from "node:http";
import { readFileSync, existsSync } from "node:fs";
import { join, extname } from "node:path";

const YAYIN = readFileSync("guncel.txt", "utf8").trim();
const TUR = {
  ".html": "text/html; charset=utf-8",
  ".css": "text/css; charset=utf-8",
  ".js": "text/javascript; charset=utf-8",
};

createServer((istek, yanit) => {
  yanit.sendDate = false;
  const parca = istek.url.split("?")[0].split("/").filter(Boolean);
  const onizleme = parca[0] === "o";
  const kimlik = onizleme ? parca[1] : YAYIN;
  const yol = (onizleme ? parca.slice(2) : parca).join("/") || "belge.html";

  const bildirimYolu = join("dagitim", kimlik, "bildirim.json");
  if (!existsSync(bildirimYolu)) {
    yanit.writeHead(404, { "Content-Type": "text/plain" }).end("dagitim yok\n");
    return;
  }
  const bildirim = JSON.parse(readFileSync(bildirimYolu, "utf8"));
  const blok = bildirim.dosyalar[yol];
  if (!blok) {
    yanit.writeHead(404, { "Content-Type": "text/plain" }).end("dosya yok\n");
    return;
  }
  const govde = readFileSync(join("depo", blok));
  const baslik = {
    "Content-Type": TUR[extname(yol)] ?? "application/octet-stream",
    "Content-Length": govde.length,
    "X-Dagitim": kimlik,
    "X-Dal": bildirim.dal,
  };
  // Onizleme dagitimlari dizinlenmemeli ve yayin adresiyle karistirilmamalidir.
  if (onizleme) baslik["X-Robots-Tag"] = "noindex, nofollow";
  yanit.writeHead(200, baslik).end(govde);
}).listen(8183, "127.0.0.1", () => console.log("sunucu: 127.0.0.1:8183"));
```

Aşağıdaki betik iki dosyayla aynı dizinde çalıştırılır. Bağlantı noktası 8183 keyfîdir ve boş
olmalıdır; kullanımdaysa iki dosyada da değiştirilir.

```bash
#!/usr/bin/env bash
# Uc dagitimi uretir, hepsini ayni sunucudan yan yana sunar.
node onizleme.mjs
ESIK=$(awk '$1 == "esik-uyarisi" { print $2 }' kimlikler.txt)
TARIH=$(awk '$1 == "tarih-bicimi" { print $2 }' kimlikler.txt)

node onizleme-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

echo "--- yayin (kok) ---"
curl -sS -D - -o /dev/null http://127.0.0.1:8183/ | grep -iE '^(x-dagitim|x-dal|x-robots-tag)'
curl -sS http://127.0.0.1:8183/varlik/panel.js

echo "--- onizleme: esik-uyarisi ---"
curl -sS -D - -o /dev/null "http://127.0.0.1:8183/o/$ESIK/" | grep -iE '^(x-dagitim|x-dal|x-robots-tag)'
curl -sS "http://127.0.0.1:8183/o/$ESIK/varlik/panel.js"
curl -sS "http://127.0.0.1:8183/o/$ESIK/"

echo "--- onizleme: tarih-bicimi ---"
curl -sS "http://127.0.0.1:8183/o/$TARIH/varlik/grafik.js"
curl -sS -o /dev/null -w 'olmayan dagitim kimligi : %{http_code}\n' \
  http://127.0.0.1:8183/o/000000000000/

kill "$sunucu"
```

```
dal             dagitim kimligi   dosya
yayin           4eebc27e9c5c      5
esik-uyarisi    eede80ec039c      5
tarih-bicimi    779bc992a4e2      5

depo: 10 ayri blok yazildi, 5 blok yeniden kullanildi (toplam 15 dosya girisi)
ikinci derleme ayni kimlikleri verdi: true

dagitimlar arasi fark (yayin ile karsilastirma):
  esik-uyarisi    2/5 dosya farkli: belge.html, varlik/panel.js
  tarih-bicimi    3/5 dosya farkli: belge.html, varlik/grafik.js, varlik/istasyon.css
--- yayin (kok) ---
X-Dagitim: 4eebc27e9c5c
X-Dal: yayin
export const ciz = (o) => o.map((x) => x.sicaklik).join(" ");
--- onizleme: esik-uyarisi ---
X-Dagitim: eede80ec039c
X-Dal: esik-uyarisi
X-Robots-Tag: noindex, nofollow
export const ciz = (o) =>
  o.map((x) => (x.sicaklik < -20 ? "!" : "") + x.sicaklik).join(" ");
<!doctype html><html lang="tr"><body><h1>Kuzey Yamac Olcum Istasyonu</h1><p>uc: https://deneme.olcum.kuzey-yamac.example/v1</p><p>dal: esik-uyarisi</p></body></html>
--- onizleme: tarih-bicimi ---
export const eksen = (a, b) => [a, b, "UTC"];
olmayan dagitim kimligi : 404
```

Çıktı dört şey söylüyor.

**Kimlik içerikten gelir.** Aynı kaynaktan ikinci kez üretilen kimlikler özdeştir. Bu, bir
dağıtımın hangi kaynaktan geldiğini adres üzerinden doğrulanabilir kılar ve sonraki dersteki
geri alma işleminin dayanağıdır: geri dönülecek sürüm bir ada değil, bir içeriğe karşılık
gelir.

**Ortam kimliğe girer.** Eşik uyarısı dalında yalnız bir dosya değiştirildiği hâlde yayından iki
dosya farklıdır; ikincisi belgedir ve farkın nedeni ölçüm ucunun değişmesidir. Derleme zamanı
ikamesinin Ortam Yapılandırması dersinde söylenen sonucu burada ölçülür: ortam değiştiğinde çıktı
baytları da değişir, dolayısıyla önizlemede sınanan baytlar yayına giden baytlar değildir. Bu
farkı sıfırlamanın tek yolu, ortama bağlı değerleri çalışma zamanı yapılandırmasına taşımaktır.

**Depo blokları paylaşır.** On beş dosya girişi on ayrı bloka indi. Oran, dosya sayısı
büyüdükçe iyileşir: bir dağıtımın maliyeti bütün çıktısı değil, yalnız değişen dosyalarıdır.
Yüzlerce dosyalık bir yayında tek satırlık bir değişiklik birkaç yeni blok yazdırır.

**Önizleme yayından ayrı bir kimliktir.** Yanıt başlıkları hangi dağıtımın sunulduğunu söyler,
önizlemeler dizinlenmeye kapalı işaretlenir ve tanımsız bir kimlik `404` alır. Kökteki yayın,
önizlemelerin varlığından etkilenmez.

#### Veri ve Yan Etki Yalıtımı

Önizlemenin en kolay bölümü baytlardır; zor bölümü, o baytların çalıştığında neye dokunduğudur.

Kural tek cümledir: **önizleme yayın verisine yazamaz.** Uygulanışı ise bir kod kararı değil, bir
yapılandırma kararıdır. Ortam Yapılandırması dersindeki kapsam bildirimi burada ikinci işini
görür: veri ucunun adresi, yazma anahtarının hangi hesaba ait olduğu ve dışa giden çağrıların
nereye gittiği ortam değişkenleriyle belirlenir. Önizleme ortamı bu değişkenleri deneme
karşılıklarına bağlar.

Ayrımın kodda `if (onizleme)` biçiminde bir dala dönüşmesi yanlıştır. O dal yayın çıktısında
da bulunur ve koşulunun yanlış değerlendiği her durumda yalıtım kaybolur. Sınırı ortam
çizerse, yayın çıktısında deneme ucuna giden bir yol hiç bulunmaz.

Üç yan etki sınıfı ayrı ayrı ele alınır. **Yazma işlemleri** ayrı bir veri kopyasına gider;
kopya tohumlanmış küçük bir kümedir, yayın verisinin aynası değildir — aynası olsaydı kişisel
veri önizleme adresinden görünür olurdu. **Dışa giden bildirimler** — e-posta, ödeme çağrısı,
üçüncü taraf tetikleyicileri — önizlemede ya kapalıdır ya bir toplayıcıya yönlendirilir.
**Şema değişiklikleri** önizlemenin kendi kopyasında uygulanır; paylaşılan bir şema üzerinde
uygulanan bir göç, henüz yayına girmemiş bir değişikliği yayına sokar.

Erişim de yalıtımın parçasıdır. `X-Robots-Tag` başlığı yalnız dizinleyicilere yöneliktir;
adresi bilen birini engellemez. Kimliğin tahmin edilemez olması bir erişim denetimi
değildir — adres tarayıcı geçmişine, bağlantı önizlemelerine ve yönlendiren başlıklarına
düşer. Erişim denetimi ayrı bir katmandır.

#### Ömür, Maliyet ve Temizlik

Değişiklik başına ortam, dağıtım sayısını değişiklik sayısıyla çarpar. Maliyetin iki kalemi
vardır ve ikisi de sınırlanabilir.

**Depolama** paylaşılan blok deposuyla sınırlanır. Bir dağıtımı silmek, bildirimini silmektir;
blokların silinmesi ancak başka hiçbir bildirimin onlara başvurmadığı doğrulandıktan sonra
yapılır. Başvuru sayımı yapılmadan yapılan bir temizlik, yayın dağıtımının dosyalarını da
silebilir.

**Çalışan kod** varsa, ikinci dersteki üç biçimden hangisinin seçildiği doğrudan maliyete
yansır. Önizlemeler seyrek ve düzensiz kullanılır; bu, o dersin tablosunda istek başına
örneğin en iyi göründüğü penceredir.

Ömür kuralı basittir: değişiklik kapandığında önizleme kaldırılır. Bir istisnası vardır —
**yayına giren her dağıtım korunur.** Korunan dağıtımlar bir sürüm geçmişi oluşturur ve
sonraki dersin geri alma işlemi tam olarak bu geçmişe döner.

#### Özet

- Önizleme dağıtımı altı koşulu birden sağlamalıdır: yayınla aynı hat, türetilmiş adres,
  yayınla yan yana yaşayabilme, veri yalıtımı, dizinlenmeme ve sonlu ömür.
- Dağıtım kimliği yol-karma çiftlerinin karmasıdır; aynı kaynak aynı kimliği verir ve kimlik
  içeriğin doğrulanabilir adı olur.
- Ortam değişkenleri çıktının baytlarına girdiği için önizleme ile yayın baytları ayrışır;
  ayrımı sıfırlamanın yolu değerleri çalışma zamanı yapılandırmasına taşımaktır.
- İçerik adresli depo blokları paylaştırır: örnekte on beş dosya girişi on bloka indi, bir
  dağıtımın maliyeti yalnız değişen dosyalarıdır.
- Yalıtım bir kod dalıyla değil, ortam yapılandırmasıyla kurulur; yazma, dışa giden bildirim
  ve şema değişikliği ayrı ayrı ele alınır.
- Dizinlenmeme başlığı erişim denetimi değildir; dağıtım silme işlemi blok başvuru sayımına
  bakmalı, yayına giren dağıtımlar ise korunmalıdır.

#### Sonraki Adım

Önizlemede sınanmış bir değişiklik, yayına girdiğinde bütün kullanıcılara aynı anda ulaşır.
Bu, önizlemenin yakalayamadığı her kusurun aynı anda herkese ulaşması demektir: yalnız gerçek
trafikte ortaya çıkan yük davranışları, yalnız gerçek veride bulunan uç durumlar ve yalnız
belirli aygıtlarda görülen hatalar. Sonraki ders yayını bir anahtar çevirmesi olmaktan
çıkarır; trafiğin küçük bir payını yeni sürüme yönlendirir, hata oranını bir eşiğe karşı
izler ve eşik aşıldığında geri döner. Geri dönülecek yer, bu derste korunan dağıtımlardır ve
dönüş süresi ölçülebilir bir büyüklüktür.

### Aşamalı Yayın ve Geri Alma

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/asamali-yayin-ve-geri-alma

Yayın riskinin trafiğin payıyla sınırlanması; yapışkan atama, izleme penceresi ve gürültüye dayanıklı eşikle otomatik geri alma, işaretçi değişimiyle atomik sürüm geçişi ve geri alınamayan değişikliklerin genişlet–daralt sırasıyla ele alınması.

Önizlemede sınanmış bir değişiklik yayına girdiğinde bütün kullanıcılara aynı anda ulaşır.
Önizlemenin yakalayamadığı her kusur da öyle: yalnız gerçek trafikte ortaya çıkan yük
davranışları, yalnız gerçek veride bulunan uç durumlar, yalnız belirli aygıtlarda görülen
hatalar.

Bu ders yayını bir anahtar çevirmesi olmaktan çıkarır. Trafiğin küçük bir payı yeni sürüme
yönlendirilir, hata oranı bir eşiğe karşı izlenir ve eşik aşıldığında önceki dağıtıma dönülür.
Sorusu şudur: pay, eşik ve geri alma süresi hangi ölçülere göre seçilir?

#### Aşamalı Yayının Dört Parçası

**Pay planı.** Yeni sürüme yönlendirilen trafik oranı, aralarında gözlem süresi bırakılan
adımlarla artırılır. Her adım bir karar noktasıdır: devam mı, geri mi.

**Yapışkan atama.** Bir kullanıcının hangi sürümü göreceği, kimliğinden türetilen bir kovayla
belirlenir ve yayın boyunca değişmez. Atama isteğe göre yapılırsa aynı kullanıcı iki sürüm
arasında gidip gelir; bunun üç somut sonucu vardır — belgenin taşıdığı karmalı adlar öteki
sürümün parçalarını istemeye başlar, oturum içinde yarım kalan akışlar bozulur ve ölçüm
kirlenir. Kova sayısı payın çözünürlüğünü verir: yüz kova, yüzde bir adım demektir.

**İzleme penceresi ve eşik.** Karar, son bir pencerede yeni sürüme düşen isteklerin hata
oranına bakılarak verilir. Pencerenin en az bir örneklem büyüklüğüne ulaşmış olması gerekir;
ulaşmadan verilen karar gürültüdür.

**Geri alma.** Eşik aşıldığında payın sıfırlanması. Bunun ön koşulu, dönülecek dağıtımın
hâlâ yayında duruyor olmasıdır — üçüncü dersteki korunan dağıtımlar kuralı burada işe yarar.

#### Aynı Kusurun Dört Yayın Biçimindeki Bedeli

```js
// asamali.mjs — yuzde payli yayin, yapiskan atama ve esige gore geri alma.
// Sanal saat kullanilir; Date.now() cagrilmaz, sonuc her kosuda aynidir.
import { createHash } from "node:crypto";

// Belirlenimci uretec (splitmix32). Tohum sabit oldugu icin dizi her kosuda aynidir.
function uretec(tohum) {
  let s = tohum >>> 0;
  return () => {
    s = (s + 0x9e3779b9) >>> 0;
    let z = s;
    z = Math.imul(z ^ (z >>> 16), 0x21f0aaad) >>> 0;
    z = Math.imul(z ^ (z >>> 15), 0x735a2d97) >>> 0;
    return ((z ^ (z >>> 15)) >>> 0) / 4294967296;
  };
}

const SURE = 3600;        // s
const HIZ = 40;           // istek/s
const KULLANICI = 5000;
const ESKI_HATA = 0.004;  // eski surumun taban hata orani
const PLAN = [[0, 1], [300, 5], [600, 25], [1200, 50], [1800, 100]];
const PENCERE = 120;      // s, izleme penceresi
const EN_AZ_ORNEK = 200;  // pencerede gereken en az yeni surum istegi
const ESIK = 0.02;        // pencere hata orani esigi
const ADIM = 30;          // s, degerlendirme araligi

// Esik, ornekleme gurultusunu asacak kadar gecilmelidir: oranin alt siniri bakilir.
const altSinir = (hatali, n) => {
  const p = hatali / n;
  return p - 2 * Math.sqrt((p * (1 - p)) / n);
};

// Yapiskan atama: kullanici kimligi 0-99 arasi bir kovaya sabitlenir.
// Ayni kullanici yayin boyunca ayni tarafta kalir.
const kova = (kullanici) =>
  createHash("sha256").update("kullanici-" + kullanici).digest().readUInt32BE(0) % 100;

const KOVA = Array.from({ length: KULLANICI }, (_, i) => kova(i));
const payi = (t) => PLAN.filter(([an]) => an <= t).at(-1)[1];

function yayinla({ yeniHata, tekSeferde, elle }) {
  // Iki ayri akis: kullanici secimi ile hata cekilisi birbirini etkilemesin.
  const rKullanici = uretec(20_260_412);
  const rHata = uretec(78_345_921);
  const olay = [];   // { t, yeni, hata, kullanici }
  let geriAlma = null, sonrakiDegerlendirme = ADIM;
  const gunluk = [];
  let ilkPay = null;

  for (let n = 0; n < SURE * HIZ; n++) {
    const t = n / HIZ;
    if (elle !== undefined && geriAlma === null && t >= elle) {
      geriAlma = elle;
      gunluk.push([geriAlma, "elle saptandi ve geri alindi"]);
    }
    while (t >= sonrakiDegerlendirme) {
      if (geriAlma === null && elle === undefined) {
        const pencere = olay.filter((o) => o.yeni && o.t > sonrakiDegerlendirme - PENCERE);
        const hatali = pencere.filter((o) => o.hata).length;
        const n = pencere.length;
        if (n >= EN_AZ_ORNEK && altSinir(hatali, n) > ESIK) {
          geriAlma = sonrakiDegerlendirme;
          gunluk.push([geriAlma, "geri alindi, pencere hata orani %" +
            ((100 * hatali) / n).toFixed(1) + " (" + hatali + "/" + n +
            "), alt sinir %" + (100 * altSinir(hatali, n)).toFixed(1)]);
        }
      }
      sonrakiDegerlendirme += ADIM;
    }
    const pay = geriAlma !== null ? 0 : tekSeferde ? 100 : payi(t);
    if (pay !== ilkPay && geriAlma === null) {
      gunluk.push([t, "pay %" + pay]);
      ilkPay = pay;
    }
    const kullanici = Math.floor(rKullanici() * KULLANICI);
    const yeni = KOVA[kullanici] < pay;
    const hata = rHata() < (yeni ? yeniHata : ESKI_HATA);
    olay.push({ t, yeni, hata, kullanici });
  }

  const yeniOlay = olay.filter((o) => o.yeni);
  const hatali = yeniOlay.filter((o) => o.hata);
  return {
    geriAlma,
    yeniIstek: yeniOlay.length,
    hata: hatali.length,
    kullanici: new Set(hatali.map((o) => o.kullanici)).size,
    gunluk,
  };
}

console.log("yuk: " + (SURE * HIZ).toLocaleString("tr-TR") + " istek / " + SURE +
  " s, " + KULLANICI.toLocaleString("tr-TR") + " kullanici, yapiskan atama");
console.log("plan: " + PLAN.map(([t, p]) => t + " s %" + p).join(", "));
console.log("izleme: " + PENCERE + " s pencere, en az " + EN_AZ_ORNEK +
  " ornek, esik %" + (100 * ESIK).toFixed(1) + ", degerlendirme her " + ADIM + " s");
console.log("eski surumun taban hata orani: %" + (100 * ESKI_HATA).toFixed(1));

for (const [ad, ayar] of [
  ["saglikli yeni surum (hata orani %0.5)", { yeniHata: 0.005 }],
  ["kusurlu yeni surum (hata orani %6.0)", { yeniHata: 0.06 }],
]) {
  const s = yayinla(ayar);
  console.log("\nsenaryo: " + ad);
  for (const [t, ileti] of s.gunluk)
    console.log("  " + (t.toFixed(0) + " s").padStart(8) + "  " + ileti);
  console.log("  sonuc: " + (s.geriAlma === null ? "yayin tamamlandi" : "geri alindi") +
    ", yeni surume dusen " + s.yeniIstek.toLocaleString("tr-TR") + " istek, " +
    s.hata + " hata, " + s.kullanici + " ayri kullanici etkilendi");
}

console.log("\nayni kusur (%6.0), dort yayin ve saptama bicimi:");
console.log("  " + "bicim".padEnd(34) + "saptama".padStart(9) + "hata".padStart(8) +
  "etkilenen kullanici".padStart(21));
for (const [ad, ayar] of [
  ["asamali + otomatik izleme", { yeniHata: 0.06 }],
  ["tek seferde + otomatik izleme", { yeniHata: 0.06, tekSeferde: true }],
  ["asamali + elle saptama (600 s)", { yeniHata: 0.06, elle: 600 }],
  ["tek seferde + elle saptama (600 s)", { yeniHata: 0.06, tekSeferde: true, elle: 600 }],
]) {
  const s = yayinla(ayar);
  console.log("  " + ad.padEnd(34) + (s.geriAlma + " s").padStart(9) +
    String(s.hata).padStart(8) +
    (s.kullanici + " (%" + ((100 * s.kullanici) / KULLANICI).toFixed(1) + ")").padStart(21));
}
```

```
$ node asamali.mjs
yuk: 144.000 istek / 3600 s, 5.000 kullanici, yapiskan atama
plan: 0 s %1, 300 s %5, 600 s %25, 1200 s %50, 1800 s %100
izleme: 120 s pencere, en az 200 ornek, esik %2.0, degerlendirme her 30 s
eski surumun taban hata orani: %0.4

senaryo: saglikli yeni surum (hata orani %0.5)
       0 s  pay %1
     300 s  pay %5
     600 s  pay %25
    1200 s  pay %50
    1800 s  pay %100
  sonuc: yayin tamamlandi, yeni surume dusen 90.818 istek, 479 hata, 451 ayri kullanici etkilendi

senaryo: kusurlu yeni surum (hata orani %6.0)
       0 s  pay %1
     300 s  pay %5
     420 s  geri alindi, pencere hata orani %9.4 (20/212), alt sinir %5.4
  sonuc: geri alindi, yeni surume dusen 318 istek, 30 hata, 30 ayri kullanici etkilendi

ayni kusur (%6.0), dort yayin ve saptama bicimi:
  bicim                               saptama    hata  etkilenen kullanici
  asamali + otomatik izleme             420 s      30            30 (%0.6)
  tek seferde + otomatik izleme          30 s      67            67 (%1.3)
  asamali + elle saptama (600 s)        600 s      61            53 (%1.1)
  tek seferde + elle saptama (600 s)    600 s    1490         1275 (%25.5)
```

Çıktı dört sonuç verir.

**Küçük pay karar veremez.** Kusurlu sürüm yüzde bir payındayken saptanmadı. Neden aritmetiktir:
saniyede kırk isteğin yüzde biri saniyede 0,4 istektir; yüz yirmi saniyelik pencerede bu
elli isteğe bile ulaşmaz ve iki yüzlük en az örneklem koşulunu sağlamaz. Saptama, pay yüzde
beşe çıktıktan sonra, pencere dolar dolmaz gerçekleşti. **İlk adımın payı, risk iştahından
değil, karar için gereken örneklem büyüklüğünden hesaplanır.**

**Eşik gürültüyle birlikte tanımlanır.** Karar, hata oranının nokta değerine değil alt
sınırına bakar: kusurlu sürümde oran yüzde 9,4, alt sınırı yüzde 5,4'tür ve yüzde 2'lik
eşiği bu alt sınır aşar. Nokta değere bakan bir kural, sağlıklı sürümde de zaman zaman eşiği
aşan pencereler bulur — iki yüz örnekte beş hata görmek, gerçek oranı yüzde 0,5 olan bir
sürüm için sıra dışı değildir. Alt sınır koşulu bu yanlış geri almaları eler.

**Aşamalı yayın saptamayı yavaşlatır ama etkiyi küçültür.** Tek seferde yayında kusur otuz
saniyede saptanır, aşamalıda dört yüz yirmi saniyede. Buna karşılık etkilenen kullanıcı sayısı
otomatik izlemede iki kat, elle saptamada yirmi dört kat farklıdır. Aşamalı yayının sattığı şey
hız değil, **yanılma payıdır**.

**Otomatik izleme olmadan aşamalı yayın da yetmez.** Son iki satır aynı on dakikalık saptama
gecikmesini iki yayın biçimine uygular: aşamalıda elli üç kullanıcı, tek seferde bin iki yüz
yetmiş beş kullanıcı — kullanıcıların dörtte biri — etkilenir. Pay planı ile izleme birlikte
çalışır; biri olmadan öteki eksik kalır.

#### Geri Alınabilir Olmanın Koşulu

Geri alma, eski kaynağı yeniden derleyip yayınlamak değildir; o bir yeni yayındır ve derleme
süresi kadar sürer. Geri almanın kısa olması için iki koşul gerekir: **dönülecek dağıtımın
hâlâ yayında duruyor olması** ve **hangisinin canlı olduğunun tek bir işaretçiyle
belirlenmesi.**

İşaretçinin değişimi atomik olmalıdır: geçici bir dosyaya yazılır, sonra `rename(2)` ile
eskisinin üzerine taşınır. Bu çağrı ya tümüyle olur ya hiç olmaz; okuyan taraf ya eski ya
yeni değeri görür, yarısını görmez.

```js
// degistir.mjs — surum etiketli dagitimlar arasinda atomik gecis.
// Once gecici bir isaretci dosyasi yazilir, sonra rename(2) ile yerine tasinir.
import { writeFileSync, renameSync, readFileSync } from "node:fs";

const hedef = process.argv[2];
const t0 = process.hrtime.bigint();
writeFileSync("yayin/guncel.yeni", hedef + "\n");
renameSync("yayin/guncel.yeni", "yayin/guncel");
const t1 = process.hrtime.bigint();

console.log("gecis -> " + hedef + "   yazma + rename " +
  (Number(t1 - t0) / 1e6).toFixed(3) + " ms   isaretci " +
  readFileSync("yayin/guncel", "utf8").trim());
```

```js
// okuyucu.mjs — gecis surerken surekli okur; kirik ya da karisik okuma sayilir.
import { readFileSync } from "node:fs";

const sayim = new Map();
let kirik = 0, tutarsiz = 0;
for (let i = 0; i < 120_000; i++) {
  try {
    const surum = readFileSync("yayin/guncel", "utf8").trim();
    const govde = readFileSync("yayin/surum/" + surum + "/belge.txt", "utf8").trim();
    if (govde !== surum) tutarsiz += 1;
    sayim.set(surum, (sayim.get(surum) ?? 0) + 1);
  } catch {
    kirik += 1;
  }
}
console.log("okuma  : " + [...sayim].sort().map(([k, v]) => k + "=" + v).join("  ") +
  "   kirik=" + kirik + "   tutarsiz=" + tutarsiz);
```

```bash
#!/usr/bin/env bash
# Surum etiketli iki dagitim, atomik gecis ve geri alma.
rm -rf yayin
mkdir -p yayin/surum/2026.03 yayin/surum/2026.04
echo "2026.03" > yayin/surum/2026.03/belge.txt
echo "2026.04" > yayin/surum/2026.04/belge.txt
echo "2026.03" > yayin/guncel
echo "baslangic: $(cat yayin/guncel)"

node okuyucu.mjs &
okuyucu=$!
sleep 0.3
node degistir.mjs 2026.04
wait "$okuyucu"

node okuyucu.mjs &
okuyucu=$!
sleep 0.3
node degistir.mjs 2026.03
wait "$okuyucu"
```

```
baslangic: 2026.03
gecis -> 2026.04   yazma + rename 0.250 ms   isaretci 2026.04
okuma  : 2026.03=18088  2026.04=101912   kirik=0   tutarsiz=0
gecis -> 2026.03   yazma + rename 0.292 ms   isaretci 2026.03
okuma  : 2026.03=101743  2026.04=18257   kirik=0   tutarsiz=0
```

Okuma sayıları makineye ve yüke bağlıdır; her koşuda değişir. Değişmeyen iki alan sonuncu
ikisidir: hiçbir okuma kırılmaz ve hiçbir okuma işaretçiyle içeriğin uyuşmadığı bir sonuç
vermez. Yüz yirmi bin okumanın her biri ya eski ya yeni sürümü görür.

Geçişin kendisi bir milisaniyenin altındadır. Buradan geri alma süresinin nereden geldiği de
çıkar: süre, işaretçinin değişmesinden değil, **değişimin yayılmasından** gelir. Birinci
dersteki geçersiz kılma ne anında yayılır ne de kullanıcının kendi önbelleğine ulaşır;
belgeye kısa tazelik penceresi vermek bu yüzden geri alma süresinin üst sınırını da belirler.
Karmalı adlar sayesinde eski sürümün varlıkları hâlâ yayındadır, dolayısıyla geri alma yeni
bir varlık indirmesi gerektirmez.

#### Geri Alınamayan Değişiklikler

Kodun geri alınması, kodun yaptığı işin geri alınması değildir. Üç sınıf değişiklik bu
kuralın dışında kalır ve aşamalı yayının koruması onlarda çalışmaz.

**Şema değişikliği.** Yeni sürüm bir alanı kaldırdıysa, geri alınan eski sürüm o alanı arar
ve bulamaz. Çözüm, değişikliği geri alınabilir adımlara bölmektir: **genişlet–daralt
(expand–contract)**. Önce şema, iki sürümün de çalışabileceği biçime genişletilir — yeni alan
eklenir, eskisi durur. Sonra kod yayınlanır ve kararlılığı beklenir. Ancak bundan sonra, ayrı
bir yayında, eski alan daraltılır. Her adımın kendi geri alınabilirliği vardır.

**Yeni biçimde yazılmış veri.** Yeni sürümün ürettiği kayıtları eski sürüm okuyamıyorsa geri
alma veriyi okunamaz bırakır. Kural, okuma desteğinin yazma değişikliğinden **bir yayın önce**
girmesidir: önce her iki biçimi de okuyabilen sürüm yayınlanır, sonra yeni biçimde yazan
sürüm.

**Dışa çıkmış yan etkiler.** Gönderilmiş bildirim, yapılmış ödeme çağrısı ya da üçüncü
tarafta oluşmuş kayıt geri alınamaz. Bunlar için tek yol ileri düzeltmedir: kusuru gideren
yeni bir sürümü yayınlamak. Aşamalı yayının burada sağladığı tek şey, yan etkinin ulaştığı
kullanıcı sayısını küçük tutmuş olmasıdır.

Üçü birlikte bir tasarım kuralı verir: **geri alınabilirlik bir özelliktir, kendiliğinden
gelmez.** Bir değişiklik geri alınamaz hâle geldiği anda, aşamalı yayın yalnız etkiyi
küçültür; düzeltmeyi kolaylaştırmaz.

#### Özet

- Aşamalı yayın dört parçadan oluşur: pay planı, yapışkan atama, izleme penceresi ve eşik,
  geri alma. Kova sayısı payın çözünürlüğünü belirler.
- İlk adımın payı risk iştahından değil, karar için gereken örneklem büyüklüğünden
  hesaplanır; örnekte yüzde birlik pay pencereyi dolduramadığı için karar veremedi.
- Eşik, hata oranının nokta değerine değil alt sınırına uygulanır; nokta değere bakan bir
  kural sağlıklı sürümleri de geri alır.
- Aşamalı yayın saptamayı yavaşlatır ama etkiyi küçültür; saptama gecikmesi büyüdükçe fark
  büyür — örnekte elli üç kullanıcıya karşılık bin iki yüz yetmiş beş kullanıcı.
- Geri alma, korunan bir dağıtıma tek bir işaretçiyle dönmektir; `rename(2)` ile yapılan
  değişim bir milisaniyenin altındadır ve süreyi belirleyen şey değişimin yayılmasıdır.
- Şema değişiklikleri genişlet–daralt sırasıyla, yazma biçimi değişiklikleri okuma desteği
  bir yayın önde olacak biçimde yapılır; dışa çıkmış yan etkiler yalnız ileri düzeltmeyle
  karşılanır.

#### Sonraki Adım

Buraya kadarki her dağıtım biçimi aynı varsayımı paylaşıyordu: çıktı bir adrese konur,
kullanıcı o adresi bir tarayıcıyla açar ve yeni sürüm bir sonraki istekte geçerli olur. Bu
varsayımın geçerli olmadığı hedefler vardır. Aynı derleme çıktısı bir masaüstü kabuğunun
içine, bir yerel gövdenin içindeki web görünümüne ya da bir uygulama mağazasından dağıtılan
pakete konabilir. O zaman adresler göreli olmak zorunda kalır, güncelleme kanalı bir inceleme
sürecinden geçer ve geri alma bu derste ölçülen milisaniyelerle değil günlerle anılır. Son
ders bu hedefleri ve getirdikleri kısıtları ele alır.

### Web Dışı Hedefler

Source: https://academia.sh/tr/kurslar/olusturma-stratejileri/web-disi-hedefler

Aynı derleme çıktısının masaüstü kabuğu, yerel gövde içine gömülü web görünümü ve uygulama mağazası paketiyle dağıtılması; kırılan kök varsayımının denetlenmesi, köprü yüzeyinin daraltılması ve kabuk ile içerik sürümlerinin ayrışması.

Buraya kadarki her dağıtım biçimi aynı varsayımı paylaşıyordu: çıktı bir adrese konur,
kullanıcı o adresi bir tarayıcıyla açar ve yeni sürüm bir sonraki istekte geçerli olur.

Bu varsayımın geçerli olmadığı hedefler vardır. Aynı derleme çıktısı bir masaüstü kabuğunun
içine, bir yerel gövdenin içindeki web görünümüne ya da bir uygulama mağazasından dağıtılan
pakete konabilir. Bu ders, hedef değiştiğinde hangi varsayımların kırıldığını ve her kırığın
nasıl kapatıldığını ele alır.

#### Değişen Dört Varsayım

**Yükleme kaynağı ve kök.** Belge bir HTTP kökünden değil dosya sisteminden yükleniyorsa
`/` ile başlayan hiçbir bağ uygulamanın dizinine çözülmez. Kök-göreli her bağ kırılır.

**Köken.** Yerel bir dosyadan yüklenen belgenin kökeni ya boştur ya benzersizdir. Aynı köken
politikası, depolama anahtarları ve Service Worker kaydı köken üzerinden tanımlı olduğu için,
web'de çalışan bir depolama şeması kabuk içinde başka bir anahtar uzayına düşer ya da hiç
kullanılamaz. Çerezler köken değil alan adı üzerinden tanımlanır, ama boş kökende onların da
taşıyacağı bir alan adı yoktur.

**Güncelleme kanalı.** Web'de yeni sürüm bir sonraki istekte geçerlidir; bir önceki dersin
işaretçi değişimi bir milisaniyenin altındaydı. Kabuk tarafında güncelleme ya bir indirme ya
bir inceleme sürecidir ve geri alma milisaniyelerle değil günlerle anılır.

**Ağ varsayımı.** Kabuk çevrimdışı açılabilir. Tarayıcı ve Web Platformu kursundaki
çevrimdışı stratejiler burada bir seçenek değil, varsayılan davranıştır.

Bir de sessiz bir ödünleşim değişikliği vardır. Web'de kod bölmenin kazancı ilk boyamayı
hızlandırmasıydı; paketlenmiş bir hedefte bütün parçalar zaten yerel diskte durur. Kod
bölmenin oradaki karşılığı **güncelleme boyutudur**: değişmeyen parçalar yeniden indirilmez.

#### Mutlak Yol Varsayımı

Birinci kırık mekanik olarak saptanabilir ve büyük ölçüde mekanik olarak kapatılabilir.

```js
// yol-denetimi.mjs — cikti agacindaki mutlak yol varsayimlarini bulur ve goreliye cevirir.
import { posix } from "node:path";

// Cikti agaci: yol -> govde. Butun baglar kok-goreli yazilmis durumda.
const CIKTI = {
  "belge.html":
    '<link rel="stylesheet" href="/varlik/istasyon.css">\n' +
    '<script type="module" src="/varlik/giris.js"></script>\n' +
    '<img src="/varlik/logo.svg" alt="Kuzey Yamac">\n',
  "arsiv/2026-03.html":
    '<link rel="stylesheet" href="/varlik/istasyon.css">\n' +
    '<script type="module" src="/varlik/giris.js"></script>\n',
  "varlik/istasyon.css":
    '.harita { background-image: url("/varlik/harita.svg"); }\n' +
    '@font-face { font-family: "Olcum"; src: url("/varlik/olcum.woff2") format("woff2"); }\n',
  "varlik/giris.js":
    'import { ciz } from "/varlik/panel.js";\n' +
    'export const arka = (ad) => fetch("/veri/" + ad + ".json");\n',
};

const KALIP = [
  [/((?:href|src)=")(\/[^"]+)(")/g, "isaretleme bagi"],
  [/(url\(")(\/[^"]+)("\))/g, "bicem url()"],
  [/(from ")(\/[^"]+)(")/g, "modul belirteci"],
];
// Calisma zamaninda birlestirilen yol: mekanik olarak duzeltilemez.
const BIRLESIK = /"(\/[^"]*)"\s*\+/g;

function goreli(kaynakYolu, mutlak, modulMu) {
  const g = posix.relative(posix.dirname(kaynakYolu), mutlak.slice(1));
  return modulMu && !g.startsWith(".") ? "./" + g : g;
}

const bulgu = [];
const yeniCikti = {};
for (const [yol, govde] of Object.entries(CIKTI)) {
  let sonuc = govde;
  for (const [kalip, tur] of KALIP) {
    sonuc = sonuc.replace(kalip, (t, on, mutlak, arka) => {
      const g = goreli(yol, mutlak, tur === "modul belirteci");
      bulgu.push([yol, tur, mutlak, g]);
      return on + g + arka;
    });
  }
  for (const [, mutlak] of govde.matchAll(BIRLESIK))
    bulgu.push([yol, "birlesik yol", mutlak, "ELLE"]);
  yeniCikti[yol] = sonuc;
}

console.log("dosya".padEnd(20) + "tur".padEnd(18) + "mutlak".padEnd(24) + "goreli");
for (const [yol, tur, mutlak, g] of bulgu)
  console.log(yol.padEnd(20) + tur.padEnd(18) + mutlak.padEnd(24) + g);

const kalan = Object.entries(yeniCikti)
  .flatMap(([yol, g]) => [...g.matchAll(/"(\/[^"]+)"/g)].map(([, m]) => yol + " -> " + m));
console.log("\ncevrimden sonra kalan mutlak yol: " + (kalan.join(", ") || "yok"));

console.log("\n-- cevrilmis iki dosya --");
for (const yol of ["arsiv/2026-03.html", "varlik/giris.js"])
  process.stdout.write(yol + ":\n" + yeniCikti[yol]);
```

```
$ node yol-denetimi.mjs
dosya               tur               mutlak                  goreli
belge.html          isaretleme bagi   /varlik/istasyon.css    varlik/istasyon.css
belge.html          isaretleme bagi   /varlik/giris.js        varlik/giris.js
belge.html          isaretleme bagi   /varlik/logo.svg        varlik/logo.svg
arsiv/2026-03.html  isaretleme bagi   /varlik/istasyon.css    ../varlik/istasyon.css
arsiv/2026-03.html  isaretleme bagi   /varlik/giris.js        ../varlik/giris.js
varlik/istasyon.css bicem url()       /varlik/harita.svg      harita.svg
varlik/istasyon.css bicem url()       /varlik/olcum.woff2     olcum.woff2
varlik/giris.js     modul belirteci   /varlik/panel.js        ./panel.js
varlik/giris.js     birlesik yol      /veri/                  ELLE

cevrimden sonra kalan mutlak yol: varlik/giris.js -> /veri/

-- cevrilmis iki dosya --
arsiv/2026-03.html:
<link rel="stylesheet" href="../varlik/istasyon.css">
<script type="module" src="../varlik/giris.js"></script>
varlik/giris.js:
import { ciz } from "./panel.js";
export const arka = (ad) => fetch("/veri/" + ad + ".json");
```

Çıktının söylediği üç şey var.

**Göreli yol dosyanın kendi konumuna bağlıdır.** Aynı biçem dosyası kökteki belgeden
`varlik/istasyon.css`, alt dizindeki belgeden `../varlik/istasyon.css` olur. Çevrim, dosya
başına yapılmak zorundadır.

**Modül belirteci ayrı bir kural ister.** `panel.js` biçiminde bir belirteç çıplak
belirteçtir ve modül yükleyicisi onu bir dosya olarak çözmez; `./` öneki zorunludur.

**Çalışma zamanında birleştirilen yol mekanik olarak düzeltilemez.** Son satırda kalan
`/veri/` öneki, adres bir değişkenle birleştirildiği için hiçbir dizgi çevrimiyle
yakalanamaz. Bu tür yolların tek çözümü, tabanın tek bir yerde hesaplanmasıdır: bir modül
kendi konumunu `import.meta.url` üzerinden çözer ve bütün çalışma zamanı adresleri bu tabana
göre kurulur. Denetimin işi, bu kuralın ihlallerini derlemede bulmaktır.

#### Köprü Yüzeyi

İkinci kırık bir yol sorunu değil, bir yetki sorunudur. Kabuğun web görünümüne açtığı işlev
kümesi — **köprü (bridge)** — uygulamanın yerel yetki yüzeyidir. Kural tek cümledir: köprüde
açılan bir yetenek, açtığı yetkinin **tamamını** açar. `dosyaOku(yol)`, "bir dosya okuma"
yeteneği değil, "her dosyayı okuma" yeteneğidir.

```js
// kopru.mjs — kabuk ile web gorunumu arasindaki koprunun yuzey denetimi.
// Kullanim: node kopru.mjs genis | node kopru.mjs dar
const AGIRLIK = { yok: 0, "kullanici secimi": 1, "uygulama dizini": 2, sinirsiz: 100 };

const YUZEY = {
  genis: {
    uzakIcerik: true,      // gorunume ucuncu taraf icerigi gomuluyor
    kokenDenetimi: false,  // kopru cagrisinin hangi kokenden geldigi sinanmiyor
    yetenek: [
      { ad: "dosyaOku(yol)", kapsam: "sinirsiz", dogrulama: "yok" },
      { ad: "kabukCalistir(komut)", kapsam: "sinirsiz", dogrulama: "yok" },
      { ad: "agIstegi(adres)", kapsam: "sinirsiz", dogrulama: "yok" },
      { ad: "bildirimGoster(metin)", kapsam: "yok", dogrulama: "tur" },
    ],
  },
  dar: {
    uzakIcerik: false,
    kokenDenetimi: true,
    yetenek: [
      { ad: "olcumDosyasiSec()", kapsam: "kullanici secimi", dogrulama: "tur" },
      { ad: "arsiveYaz(kayit)", kapsam: "uygulama dizini", dogrulama: "sema" },
      { ad: "bildirimGoster(metin)", kapsam: "yok", dogrulama: "tur" },
    ],
  },
};

function denetle(ad) {
  const y = YUZEY[ad];
  const bulgu = [];
  console.log("yuzey: " + ad);
  console.log("  " + "yetenek".padEnd(24) + "kapsam".padEnd(20) + "dogrulama".padEnd(12) + "agirlik");
  let toplam = 0;
  for (const c of y.yetenek) {
    toplam += AGIRLIK[c.kapsam];
    console.log("  " + c.ad.padEnd(24) + c.kapsam.padEnd(20) + c.dogrulama.padEnd(12) +
      AGIRLIK[c.kapsam]);
    if (c.kapsam === "sinirsiz") bulgu.push(["K1", c.ad, "kapsam sinirsiz"]);
    if (c.dogrulama === "yok") bulgu.push(["K2", c.ad, "parametre dogrulanmiyor"]);
  }
  if (y.uzakIcerik && !y.kokenDenetimi)
    bulgu.push(["K3", "(yuzey)", "uzak koken icerigi var, kopru koken denetimi yapmiyor"]);

  for (const [kural, hedef, ileti] of bulgu)
    console.log("  " + kural + "  " + hedef.padEnd(24) + ileti);
  console.log("  yuzey agirligi: " + toplam + "   ihlal: " + bulgu.length);
  return bulgu.length;
}

process.exitCode = denetle(process.argv[2]) > 0 ? 1 : 0;
```

```bash
#!/usr/bin/env bash
# Iki kopru yuzeyini de denetler ve cikis kodlarini yazar.
node kopru.mjs genis;  echo "cikis kodu: $?"
node kopru.mjs dar;    echo "cikis kodu: $?"
```

```
yuzey: genis
  yetenek                 kapsam              dogrulama   agirlik
  dosyaOku(yol)           sinirsiz            yok         100
  kabukCalistir(komut)    sinirsiz            yok         100
  agIstegi(adres)         sinirsiz            yok         100
  bildirimGoster(metin)   yok                 tur         0
  K1  dosyaOku(yol)           kapsam sinirsiz
  K2  dosyaOku(yol)           parametre dogrulanmiyor
  K1  kabukCalistir(komut)    kapsam sinirsiz
  K2  kabukCalistir(komut)    parametre dogrulanmiyor
  K1  agIstegi(adres)         kapsam sinirsiz
  K2  agIstegi(adres)         parametre dogrulanmiyor
  K3  (yuzey)                 uzak koken icerigi var, kopru koken denetimi yapmiyor
  yuzey agirligi: 300   ihlal: 7
cikis kodu: 1
yuzey: dar
  yetenek                 kapsam              dogrulama   agirlik
  olcumDosyasiSec()       kullanici secimi    tur         1
  arsiveYaz(kayit)        uygulama dizini     sema        2
  bildirimGoster(metin)   yok                 tur         0
  yuzey agirligi: 3   ihlal: 0
cikis kodu: 0
```

İki yüzey aynı işleri yapar; ağırlıkları arasında yüz kat fark vardır. Farkı üç kural üretir.

**Kapsam, yeteneğin kendisine gömülür.** `dosyaOku(yol)` yerine `olcumDosyasiSec()` konur:
ikincisinde dosyayı kullanıcı seçer ve seçilen tek nesne dışında hiçbir şey açılmaz. Yetki,
parametreyle değil işlevin tanımıyla sınırlanır.

**Parametre şemayla doğrulanır.** Köprüye gelen değer, web tarafından geldiği için
doğrulanmamış girdidir. Doğrulama kabuk tarafında yapılır; web tarafındaki denetim yalnız
kullanıcı deneyimi içindir.

**Köken sınanır.** Görünüme üçüncü taraf içeriği giriyorsa — gömülü bir harita, bir çerçeve —
köprü o içeriğe de açıktır. Köken denetimi yoksa, uygulamanın yerel yetkileri o içeriğin
eline geçmiş demektir. Uzak içerik yükleyen görünümlerde doğru karar köprüyü hiç bağlamamak
ve o içeriği ayrı bir görünümde tutmaktır.

#### Güncelleme Kanalı ve Sürüm Ayrışması

Üçüncü kırık, iki bileşenin ayrı kanallardan güncellenmesinden doğar. Kabuk mağaza kanalından,
web içeriği kendi kanalından gelir; ikisi aynı anda güncellenmez.

```js
// surum.mjs — kurulu kabuk surumleri ile icerik surumunun erisebildigi pay.
// Semantik surumleme: karsilastirma sayi ucluleri uzerinden yapilir.
const KURULU = {
  "1.2.0": 0.08, "1.3.0": 0.14, "2.0.0": 0.33, "2.1.0": 0.31, "3.0.0": 0.14,
};
const ICERIK = [
  { surum: "2026.02", enAzKabuk: "1.2.0" },
  { surum: "2026.03", enAzKabuk: "2.0.0" },
  { surum: "2026.04", enAzKabuk: "3.0.0" },
];

const parcala = (s) => s.split(".").map(Number);
const enAzMi = (kabuk, gereken) => {
  const a = parcala(kabuk), b = parcala(gereken);
  for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
  return true;
};

console.log("kurulu kabuk dagilimi: " +
  Object.entries(KURULU).map(([s, p]) => s + " %" + (100 * p).toFixed(0)).join(", "));
console.log("\n" + "icerik surumu".padEnd(16) + "en az kabuk".padEnd(14) +
  "erisilen pay".padStart(13) + "eski icerikte kalan".padStart(21));
for (const { surum, enAzKabuk } of ICERIK) {
  const pay = Object.entries(KURULU)
    .filter(([k]) => enAzMi(k, enAzKabuk))
    .reduce((t, [, p]) => t + p, 0);
  console.log(surum.padEnd(16) + enAzKabuk.padEnd(14) +
    ("%" + (100 * pay).toFixed(0)).padStart(13) +
    ("%" + (100 * (1 - pay)).toFixed(0)).padStart(21));
}
```

```
$ node surum.mjs
kurulu kabuk dagilimi: 1.2.0 %8, 1.3.0 %14, 2.0.0 %33, 2.1.0 %31, 3.0.0 %14

icerik surumu   en az kabuk    erisilen pay  eski icerikte kalan
2026.02         1.2.0                  %100                   %0
2026.03         2.0.0                   %78                  %22
2026.04         3.0.0                   %14                  %86
```

En az kabuk gereksinimini bir sürüm yükseltmek erişilen payı yüzde 100'den 78'e, bir sürüm
daha yükseltmek 14'e indirir. Kurulu sürüm dağılımının ilerleme hızı mağaza kanalının ve
kullanıcının güncelleme davranışının işidir; uygulamanın denetiminde değildir. Buradan iki
kural çıkar.

**İçerik sürümü kabuk sürümünün gerisinden gelir.** Yeni bir köprü yeteneğine dayanan içerik,
o yeteneği taşıyan kabuk yeterince yayılana kadar yayınlanmaz. Aradaki süre boyunca içerik
yeteneği saptayarak çalışır ve bulamazsa eski yolu kullanır.

**Uyum iki yönlüdür.** Yeni kabuk eski içeriği, eski kabuk da bir süre yeni içeriği
çalıştırabilmelidir. Köprü işlevlerinin imzaları bu yüzden geriye dönük uyumlu değişir; bir
imzanın değişmesi, o kabuğa düşen bütün eski içeriği kırar.

Geri alma da burada ayrışır. İçerik ağdan geliyorsa bir önceki dersin işaretçisi geçerlidir
ve geri alma saniyeler alır. Kabuk tarafında geri alma yoktur: yayımlanmış bir sürüm
kanaldan çekilse bile kurulu olanlar kurulu kalır. Kabuk sürümü, dördüncü dersin geri
alınamayan değişiklikler sınıfına aittir ve bu yüzden kabuğa konan mantık en aza indirilir —
değişme olasılığı olan her şey içerik tarafında tutulur.

#### Özet

- Hedef değiştiğinde dört varsayım kırılır: yükleme kökü, köken, güncelleme kanalı ve ağın
  varlığı. Kod bölmenin kazancı ilk boyamadan güncelleme boyutuna kayar.
- Kök-göreli bağlar dosya başına göreli bağlara çevrilir; modül belirteçleri `./` öneki
  ister, çalışma zamanında birleştirilen yollar tek bir tabandan çözülmelidir.
- Köprüde açılan her yetenek, açtığı yetkinin tamamını açar; kapsam yeteneğin tanımına
  gömülür, parametre kabuk tarafında şemayla doğrulanır, köken sınanır.
- Örnekte aynı işleri gören iki köprü yüzeyi arasında yüz kat ağırlık farkı vardır; uzak
  içerik yükleyen bir görünüme köprü bağlanmaz.
- Kurulu kabuk dağılımı uygulamanın denetiminde değildir: en az kabuk gereksinimini bir sürüm
  yükseltmek erişilen payı yüzde 100'den 78'e indirir.
- Kabuk sürümü geri alınamaz; değişme olasılığı olan mantık içerik tarafında tutulur ve
  köprü imzaları geriye dönük uyumlu değişir.

#### Kurs Kapanışı

Bu kurs tek bir soruyla başladı: bir arayüzün işaretlemesi nerede ve ne zaman üretilir?
Oluşturma modelleri bu soruyu altı ayrı yanıtla ölçtü — istemci, sunucu, derleme zamanı,
artımlı yeniden üretim, akışlı gönderim ve kenar — ve yanıtların hiçbirinin genel olarak
üstün olmadığını gösterdi; seçim içerik tazeliği, kişiselleştirme ve ölçek eksenlerinde
yapılan bir karardır.

Derleme ve paketleme konusu bu kararın çıktısını üretti: bağımlılık çizgesinden parçalar,
kullanılmayan kodun elenmesi, varlık boru hattı, içeriğe bağlı adlar, geliştirme kipinin ayrı
ekonomisi ve çıktıyı ortamına bağlayan derleme zamanı değişkenleri. O konunun son dersi tek
bir sınır kurdu ve bu kurs boyunca geçerli kaldı: istemci paketine giren her değer herkese
açıktır.

Dağıtım konusu çıktıyı kullanıcıya ulaştırdı. Statik barındırma her isteği bir dosyaya
eşledi ve önbellek politikasının istek isabetine çevrimini ölçtü; sunucu gerektiren dağıtım
üç barındırma biçimini ayrılmış kapasite ve soğuk başlatma eksenlerinde ayırdı; önizleme
dağıtımları her değişikliğe kendi ortamını verdi; aşamalı yayın riski trafiğin payıyla
sınırladı; bu ders de aynı çıktının web dışı hedeflerde hangi varsayımları kaybettiğini
gösterdi.

Ortak bir yöntem üç konuyu da birbirine bağlar: her karar bir ölçüye bağlandı. İlk bayt
süresi, yeniden inen bayt, isabet oranı, ayrılmış örnek-saniye, saptama süresi, etkilenen
kullanıcı sayısı. Ölçüye bağlanmayan bir dağıtım kararı, savunulabilir bir karar değildir.

Geriye ölçmenin kendisi kalıyor. Uygulama artık üretiliyor, paketleniyor ve yayınlanıyor;
sıra yayınlananın nitelikte olup olmadığını sınamakta. **M14/K08 · Frontend Kalitesi** kursu
bunu dört başlıkta yapar: başarım bütçesi tanımlamak ve ölçümle doğrulamak, erişilebilirlik
ölçütlerini standart düzeyinde uygulamak, istemci tarafındaki güvenlik risklerini savunma
katmanlarıyla azaltmak ve bütün bunları yayına girmeden yakalayan bir test düzeni kurmak. Bu
kursta kurulan sürekli tümleştirme hattı, oradaki bütçelerin ve ölçütlerin uygulanacağı yer
olacak.

