jskelet 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/AGENTS.md +136 -136
  2. package/CHANGELOG.md +628 -620
  3. package/LICENSE +21 -21
  4. package/README.md +2 -0
  5. package/bin/jskelet.mjs +130 -130
  6. package/docs/01-baslangic.md +291 -291
  7. package/docs/02-mimari.md +310 -310
  8. package/docs/03-routing.md +515 -515
  9. package/docs/04-render-ve-sablonlar.md +667 -661
  10. package/docs/05-islands.md +486 -486
  11. package/docs/06-cache.md +1467 -1443
  12. package/docs/07-yapilandirma.md +1208 -1197
  13. package/docs/08-build.md +429 -429
  14. package/docs/09-dev-araclari.md +364 -364
  15. package/docs/10-dagitim.md +348 -338
  16. package/docs/12-panel-ve-oturum.md +479 -478
  17. package/docs/README.md +83 -83
  18. package/docs/en/01-getting-started.md +298 -298
  19. package/docs/en/02-architecture.md +329 -329
  20. package/docs/en/03-routing.md +531 -531
  21. package/docs/en/04-rendering.md +675 -669
  22. package/docs/en/05-islands.md +497 -497
  23. package/docs/en/06-caching.md +1476 -1453
  24. package/docs/en/07-configuration.md +1229 -1219
  25. package/docs/en/08-build.md +447 -447
  26. package/docs/en/09-dev-tools.md +373 -373
  27. package/docs/en/10-deployment.md +351 -340
  28. package/docs/en/11-migration.md +398 -398
  29. package/docs/en/12-dashboards-and-sessions.md +489 -488
  30. package/docs/en/README.md +87 -87
  31. package/package.json +137 -137
  32. package/src/build/ensure-build.mjs +19 -19
  33. package/src/build/paths.mjs +153 -153
  34. package/src/build/resolve-peer.mjs +36 -36
  35. package/src/build/tasks/client.mjs +349 -349
  36. package/src/build/tasks/css.mjs +235 -235
  37. package/src/build/tasks/fonts.mjs +146 -146
  38. package/src/build/tasks/icons.mjs +357 -357
  39. package/src/build/tasks/images.mjs +244 -244
  40. package/src/build/tasks/precompress.mjs +78 -78
  41. package/src/build/tasks/templates.mjs +20 -20
  42. package/src/client/admin/i18n.js +764 -764
  43. package/src/client/admin/login.html +74 -74
  44. package/src/client/admin/panel.css +809 -809
  45. package/src/client/admin/panel.html +495 -495
  46. package/src/client/admin/panel.js +1251 -1251
  47. package/src/client/devtools/report.html +185 -185
  48. package/src/client/devtools/report.js +745 -745
  49. package/src/client/devtools/seo.js +628 -628
  50. package/src/client/dom.js +95 -95
  51. package/src/client/form.js +192 -192
  52. package/src/client/index.js +45 -45
  53. package/src/client/registry.js +305 -305
  54. package/src/client/safe-image.js +91 -91
  55. package/src/client/shared-cookie.js +225 -225
  56. package/src/client/store.js +36 -36
  57. package/src/client/swap.js +188 -188
  58. package/src/compile/codegen.js +336 -336
  59. package/src/compile/compile-all.js +149 -149
  60. package/src/compile/errors.js +66 -66
  61. package/src/compile/expr.js +409 -409
  62. package/src/compile/index.js +17 -17
  63. package/src/compile/parse.js +541 -541
  64. package/src/compile/resolve.js +211 -211
  65. package/src/compile/scan-exports.js +51 -51
  66. package/src/config/defaults.js +541 -534
  67. package/src/config/index.js +1500 -1469
  68. package/src/config/pattern.js +107 -107
  69. package/src/generate.mjs +163 -163
  70. package/src/http/control-flow.js +71 -71
  71. package/src/http/cookies-entry.js +21 -21
  72. package/src/http/cookies.js +277 -277
  73. package/src/http/request-cache.js +46 -46
  74. package/src/http/request-context.js +165 -165
  75. package/src/http/shared-cookie.js +178 -178
  76. package/src/index.js +101 -101
  77. package/src/init.mjs +232 -230
  78. package/src/migrate/apply.mjs +262 -262
  79. package/src/migrate/babel.mjs +79 -79
  80. package/src/migrate/classify.mjs +155 -155
  81. package/src/migrate/config.mjs +126 -126
  82. package/src/migrate/fs-walk.mjs +191 -191
  83. package/src/migrate/parse.mjs +26 -26
  84. package/src/migrate/scan.mjs +177 -177
  85. package/src/migrate/transform/expr-source.mjs +168 -168
  86. package/src/migrate/transform/island.mjs +67 -67
  87. package/src/migrate/transform/jsx-to-component.mjs +302 -302
  88. package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
  89. package/src/migrate/transform/page-split.mjs +435 -435
  90. package/src/migrate/write.mjs +81 -81
  91. package/src/migrate.mjs +171 -171
  92. package/src/runtime/alias-hooks.mjs +119 -119
  93. package/src/runtime/register.mjs +4 -4
  94. package/src/server/admin/actions.js +229 -229
  95. package/src/server/admin/auth.js +125 -125
  96. package/src/server/admin/event-log.js +151 -151
  97. package/src/server/admin/gate.js +209 -209
  98. package/src/server/admin/inventory.js +188 -188
  99. package/src/server/admin/mount.js +56 -56
  100. package/src/server/admin/router.js +216 -216
  101. package/src/server/admin/snapshot.js +241 -241
  102. package/src/server/assets.js +147 -147
  103. package/src/server/auth/handoff.js +309 -309
  104. package/src/server/cache-blob.js +70 -70
  105. package/src/server/cache-control.js +45 -0
  106. package/src/server/cache-deps.js +42 -42
  107. package/src/server/cache-vary.js +113 -113
  108. package/src/server/cloudflare.js +607 -607
  109. package/src/server/create-app.js +366 -366
  110. package/src/server/data-cache.js +553 -553
  111. package/src/server/dev/report.js +485 -485
  112. package/src/server/dev/socket.js +170 -170
  113. package/src/server/dev/version-check.mjs +139 -139
  114. package/src/server/disk-cache.js +233 -233
  115. package/src/server/ejs-adapter.js +59 -59
  116. package/src/server/html-cache.js +1196 -1196
  117. package/src/server/image-optimizer.js +500 -500
  118. package/src/server/logs/access-middleware.js +66 -66
  119. package/src/server/logs/file-sink.js +193 -193
  120. package/src/server/logs/pipeline.js +165 -165
  121. package/src/server/logs/s3-put.js +214 -214
  122. package/src/server/logs/s3-sink.js +112 -112
  123. package/src/server/metadata.js +102 -102
  124. package/src/server/middleware/compression.js +205 -205
  125. package/src/server/middleware/csrf.js +134 -134
  126. package/src/server/middleware/dev-gate.js +75 -75
  127. package/src/server/middleware/headers.js +37 -37
  128. package/src/server/middleware/redirects.js +32 -32
  129. package/src/server/middleware/robots-txt.js +341 -341
  130. package/src/server/middleware/static-precompressed.js +121 -121
  131. package/src/server/middleware/trailing-slash.js +53 -53
  132. package/src/server/middleware/upstream-proxy.js +141 -141
  133. package/src/server/og-image.js +369 -356
  134. package/src/server/port-guard.js +255 -255
  135. package/src/server/prewarm.js +1082 -1082
  136. package/src/server/redis.js +588 -588
  137. package/src/server/render.js +910 -910
  138. package/src/server/router.js +157 -157
  139. package/src/server/status-page.js +265 -265
  140. package/src/server/upstream-limiter.js +376 -376
  141. package/src/server/upstream-tracking.js +166 -166
  142. package/src/shared/cookie-domain.js +66 -66
  143. package/src/start.mjs +22 -22
  144. package/src/templates/layout.ejs +30 -30
  145. package/src/templates/layout.jsk +30 -30
  146. package/src/version.mjs +31 -31
  147. package/src/views/components/loader.js +101 -101
  148. package/src/views/helpers/html.js +102 -102
  149. package/src/views/helpers/tags.js +375 -375
  150. package/types/config/defaults.d.ts +6 -0
  151. package/types/config/index.d.ts +6 -0
  152. package/types/server/cache-control.d.ts +28 -0
  153. package/types/server/og-image.d.ts +5 -0
@@ -1,534 +1,541 @@
1
- /**
2
- * Framework varsayılanları.
3
- *
4
- * Buradaki hiçbir değer domain bilgisi taşımaz: uygulamaya özel her şey
5
- * `jskelet.config.mjs` üzerinden gelir. Varsayılanların ayrı dosyada olması,
6
- * "framework ne yapıyor, uygulama ne ekliyor" ayrımını okunur tutar.
7
- */
8
-
9
- /** Uzun süre cache'lenecek hash'li çıktı dizini. */
10
- export const IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
11
-
12
- /**
13
- * Proje kökündeki dizin adları. `jskelet.config.mjs` içinde `paths` ile
14
- * tek tek ezilebilir.
15
- */
16
- export const DEFAULT_DIRS = {
17
- /** Layout + sayfalar + bileşenler (klasik kök). */
18
- views: "views",
19
- /** Feature-first dikey dilimler (`features/<name>/{server,views,client}`). */
20
- features: "features",
21
- /** Özellikler arası paylaşılan server/views/client. */
22
- shared: "shared",
23
- /** Statik dosyalar; build çıktısı da buraya yazılır. */
24
- public: "public",
25
- /** Island runtime ve entry'ler. */
26
- client: "client",
27
- /** Route modülleri. */
28
- routes: "routes",
29
- /** Tailwind/PostCSS giriş dosyası. */
30
- styles: "styles/globals.css",
31
- /** Build ara çıktıları (manifest, metafile, images, templates). */
32
- generated: ".jskelet",
33
- };
34
-
35
- /**
36
- * Uzantı ve önek bazlı statik dosya tespiti. Bu listeye uyan yollara
37
- * `IMMUTABLE_CACHE` yazılır.
38
- */
39
- export const DEFAULT_STATIC = {
40
- extensions: [".svg", ".png", ".webp", ".avif", ".ico", ".woff2"],
41
- prefixes: ["/assets/", "/fonts/"],
42
- };
43
-
44
- /** Dev gate'in hiçbir koşulda kapatmadığı yollar. */
45
- export const DEFAULT_DEV_GATE_BYPASS = [
46
- "/api/healthcheck",
47
- "/robots.txt",
48
- "/sitemap.xml",
49
- "/site.webmanifest",
50
- "/favicon.ico",
51
- ];
52
-
53
- /**
54
- * Klasik (liste tabanlı) prewarm ayarları. `onVisit` modu bunlarla birlikte
55
- * kullanılamaz — ya açılış/`prewarmPaths` turu, ya ziyaret edilen sayfadaki
56
- * linkleri ısıtma.
57
- */
58
- export const DEFAULT_PREWARM = {
59
- enabled: true,
60
- max: 400,
61
- intervalSeconds: 0,
62
- /** Paralel ısıtma isteği; dev'de sunucu tek süreç olduğu için düşürülür. */
63
- concurrency: 4,
64
- /** İki tur arasında beklenen süre: upstream'e ani yük binmesin. */
65
- delayMs: 0,
66
- /**
67
- * Saniyedeki en fazla ısıtma isteği. 0 → sınırsız (yalnızca `concurrency`
68
- * frenler). Upstream'i kota sınırının altında tutmanın en doğrudan yolu bu:
69
- * paralellik ne kadar yükselse de tur bu hızın üstüne çıkmaz.
70
- */
71
- rps: 0,
72
- /**
73
- * Tekrar turundan önce beklenen süre. Rate limit pencereleri saniye
74
- * mertebesinde; hemen tekrar denemek aynı 429'u almak demek.
75
- */
76
- retryDelayMs: 2000,
77
- /**
78
- * Liste `max`'tan uzunsa periyodik turlar kaldığı yerden devam eder.
79
- * Böylece 10.000 yolluk bir site tek turda değil, turlar boyunca ısınır.
80
- * `priority` eşleşen yollar her turda ısıtıldığı için rotasyon yalnızca
81
- * kuyruğu dolaşır.
82
- */
83
- rotate: true,
84
- /**
85
- * Isıtma sırasını belirleyen desenler. String (`/haber/:slug`) ya da
86
- * `RegExp` kabul eder; önce yazılan önce ısınır.
87
- * @type {(string | RegExp)[]}
88
- */
89
- priority: [],
90
- /**
91
- * Klasik turda ısıtılacak origin listesi. Boşsa `http://127.0.0.1:<port>`.
92
- * `cache().vary.host` açıkken locale host'ları buraya yazılmazsa yalnızca
93
- * loopback anahtarı ısınır.
94
- * @type {string[]}
95
- */
96
- origins: [],
97
- };
98
-
99
- /**
100
- * Ziyaret tabanlı ısıtma. Bir sayfa servis edilince HTML'deki aynı-origin
101
- * linkler kuyruğa alınır; bir sonraki tıklama (veya başka ziyaretçi) çoğu
102
- * zaman HIT görür. Klasik `prewarm` alanlarıyla karşılıklı dışlayıcıdır.
103
- */
104
- export const DEFAULT_PREWARM_ON_VISIT = {
105
- enabled: false,
106
- /** Sayfa başına üstten alta en fazla kaç link kuyruğa alınır. */
107
- perPage: 20,
108
- /**
109
- * Paralel işçi. `null` yalnızca kapalı modda durur; açıkken tavan
110
- * (`ON_VISIT_CONCURRENCY_CEILING`) uygulanır. Klasik prewarm'daki
111
- * prod 4 burada geçerli değil — onVisit sürekli çalışır.
112
- * @type {number | null}
113
- */
114
- concurrency: null,
115
- /**
116
- * Saniyedeki istek tavanı. `null` yalnızca kapalı modda durur; açıkken
117
- * `0` (sınırsız) dahil her şey `ON_VISIT_RPS_CEILING` ile kesilir.
118
- * @type {number | null}
119
- */
120
- rps: null,
121
- };
122
-
123
- /**
124
- * onVisit sürekli tur olduğu için klasik prewarm'daki "sınırsız" burada yok.
125
- * Config daha yükseğini yazsa da çözümlenen değer bu tavanları geçemez.
126
- */
127
- export const ON_VISIT_PER_PAGE_CEILING = 20;
128
- export const ON_VISIT_RPS_CEILING = 2;
129
- export const ON_VISIT_CONCURRENCY_CEILING = 2;
130
-
131
- /**
132
- * Bekleyen onVisit yolları. Bir sayfa düzinelerce link basınca kuyruk
133
- * birikmesin; taşan link bu turda alınmaz.
134
- */
135
- export const ON_VISIT_QUEUE_MAX = 64;
136
-
137
- /** `onVisit` açıkken `cache().prewarm` kökünde yasak olan klasik alanlar. */
138
- export const CLASSIC_PREWARM_KEYS = [
139
- "enabled",
140
- "max",
141
- "intervalSeconds",
142
- "concurrency",
143
- "delayMs",
144
- "rps",
145
- "retryDelayMs",
146
- "rotate",
147
- "priority",
148
- "origins",
149
- ];
150
-
151
- /**
152
- * HTML önbelleğinin girdi sınırı. 500 girdi ortalama bir sayfa boyutunda
153
- * yaklaşık 100-200 MB tutar; uzun kuyruklu siteler bunu yükseltmek yerine
154
- * veri önbelleğine yaslanmalı (bkz. `DEFAULT_DATA_CACHE`).
155
- *
156
- * Config daha yükseğini istese de girdi sayısı `HTML_CACHE_MAX_ENTRIES_CEILING`
157
- * değerini geçemez. Asıl bellek freni bayt bütçesidir: şişman sayfa ve
158
- * `vary.host` kopyası sayı tavanının altında da RSS'i şişirir.
159
- */
160
- export const DEFAULT_HTML_CACHE_MAX_ENTRIES = 500;
161
-
162
- /** `cache().maxEntries` için sert tavan. Üstü uyarıyla bu değere çekilir. */
163
- export const HTML_CACHE_MAX_ENTRIES_CEILING = 800;
164
-
165
- /**
166
- * Süreç içi HTML string + sıkıştırılmış gövde tavanı (256 MB).
167
- * `install()` bunu uygular; config yükseltemez. Tek sayfa bütçeden büyükse
168
- * saklanmaz, yanıt yine gider.
169
- */
170
- export const HTML_CACHE_BYTE_BUDGET = 256 * 1024 * 1024;
171
-
172
- /**
173
- * Süreç içi veri önbelleğinin JSON bayt tavanı (64 MB). Sayı tavanı
174
- * şişman gövdeleri tutmaz; config yükseltemez. Tek değer bütçeden büyükse
175
- * saklanmaz, çağıran sonucu yine alır.
176
- */
177
- export const DATA_CACHE_BYTE_BUDGET = 64 * 1024 * 1024;
178
-
179
- /**
180
- * Uzak görsel disk önbelleğinin tavanı (256 MB). `.jskelet/image-cache/`
181
- * bu boyutu aşınca en eski dosya düşer. Config yükseltemez.
182
- */
183
- export const IMAGE_CACHE_BYTE_BUDGET = 256 * 1024 * 1024;
184
-
185
- /** `cache().data.maxEntries` için sert tavan. Uzun kuyruk burada durur, HTML'de değil. */
186
- export const DATA_CACHE_MAX_ENTRIES_CEILING = 20_000;
187
-
188
- /**
189
- * `notFound()` geçici bir upstream hatasına denk geldiğinde sayfanın kaç kez
190
- * daha denenmesi gerektiği.
191
- *
192
- * Varsayılan tek deneme: maliyeti upstream'e binen ikinci bir istek turu, ama
193
- * alternatifi var olan bir sayfayı 404 olarak servis etmek — arama motoru için
194
- * geçici bir rate limit'in kalıcı kayba dönüşmesi. `attempts: 0` tekrarı
195
- * kapatır ve doğrudan önbelleğe girmeyen 503'e düşer.
196
- */
197
- export const DEFAULT_TRANSIENT_RETRY = {
198
- attempts: 1,
199
- delayMs: 300,
200
- };
201
-
202
- /**
203
- * Upstream veri önbelleği.
204
- *
205
- * HTML önbelleğinden bilinçli olarak çok daha büyük: JSON, aynı sayfanın
206
- * HTML'ine göre onlarca kat küçük. Uzun kuyruğu (on binlerce haber/etiket)
207
- * HTML olarak tutmak imkânsız, verisini tutmak ise ucuz — ve API kotasını
208
- * koruyan katman burası.
209
- */
210
- export const DEFAULT_DATA_CACHE = {
211
- maxEntries: 10000,
212
- /**
213
- * TTL dolduktan sonra girdinin kaç TTL boyunca daha kullanılabileceği.
214
- * HTML'deki 1 katsayısından yüksek: bayat veri, eksik sayfadan iyidir ve
215
- * upstream düştüğünde tek elde kalan şey budur.
216
- */
217
- staleFactor: 10,
218
- };
219
-
220
- /**
221
- * Upstream API'ye giden isteklerin host başına hız freni.
222
- *
223
- * Varsayılan **kapalı** (`rate: 0`): fren, kotasını bilen bir uygulamanın
224
- * bilinçli kararı. Açıldığında `rate` bir tavan olur ve gerçek hız 429
225
- * cevaplarına göre kendini aşağı çeker (bkz. `src/server/upstream-limiter.js`).
226
- *
227
- * `hosts` ile tek tek uçlar ayrılabilir; API'lerin kotası aynı olmak zorunda
228
- * değil:
229
- *
230
- * upstream: { rate: 10, hosts: { "api.example.com": { rate: 3 } } }
231
- */
232
- export const DEFAULT_UPSTREAM_LIMIT = {
233
- /** Saniyedeki en fazla çağrı. 0 → fren tamamen kapalı. */
234
- rate: 0,
235
- /** Kova boyu; verilmezse bir saniyelik bütçe kadar patlamaya izin verilir. */
236
- burst: 0,
237
- /** Aynı anda uçabilecek çağrı. Ortalama hızdan bağımsız: anlık baskıyı bağlar. */
238
- concurrency: 8,
239
- /** Azalmanın dibi: hız buranın altına inmez, yoksa site tamamen durur. */
240
- minRate: 0.5,
241
- /** Toplamsal artışın adımı (çağrı/saniye) ve periyodu. */
242
- increaseStep: 1,
243
- increaseIntervalMs: 5000,
244
- /**
245
- * İki azalma arasındaki en kısa süre. Aynı anda uçan on çağrının hepsi 429
246
- * dönerse hız on kez yarılanıp dibe vurmasın.
247
- */
248
- decreaseIntervalMs: 1000,
249
- /** Devre kesici: art arda kaç 429'dan sonra host'a hiç gidilmeyeceği. */
250
- breakerFailures: 5,
251
- breakerCooldownMs: 10_000,
252
- /** @type {Record<string, Record<string, number>>} */
253
- hosts: {},
254
- };
255
-
256
- /**
257
- * Opsiyonel Redis ikinci kademesi (L2).
258
- *
259
- * Redis **birincil store değil**: bellek içi önbellek (L1) aynen kalır, Redis
260
- * iki iş yapar — L1'de bulunmayan bir sayfa için render'ı atlatmak ve
261
- * invalidation'ı bütün node'lara yaymak. Tek instance çalışan bir kurulumda
262
- * kazanç neredeyse yok; bu yüzden `enabled` varsayılan olarak kapalı.
263
- *
264
- * `storeEncoded` kapalı, çünkü sıkıştırılmış gövdeleri de paylaşmak girdi
265
- * başına boyutu iki-üç katına çıkarır ve brotli'yi yeniden üretmek Redis'ten
266
- * indirmekten çoğu zaman daha ucuz.
267
- */
268
- export const DEFAULT_REDIS = {
269
- enabled: false,
270
- /** `redis://` ya da `rediss://`. Boşsa ioredis varsayılanı (localhost:6379). */
271
- url: /** @type {string | null} */ (null),
272
- /** Aynı Redis'i paylaşan birden fazla uygulamayı ayırır. */
273
- namespace: "default",
274
- keyPrefix: "_jskelet",
275
- /** HTML gövdeleri paylaşılsın mı. */
276
- html: true,
277
- /** Veri önbelleği paylaşılsın mı. */
278
- data: true,
279
- /** Brotli/gzip gövdeleri de paylaşılsın mı. */
280
- storeEncoded: false,
281
- /** pub/sub üzerinden invalidation yayını. */
282
- events: true,
283
- /**
284
- * Tek bir komutun en fazla bekletebileceği süre. Önbellek okuması isteği
285
- * bloklayan bir adım: Redis takıldığında render'a düşmek, ağı beklemekten
286
- * iyidir.
287
- */
288
- commandTimeoutMs: 200,
289
- };
290
-
291
- /**
292
- * Kalıcı log sink'leri (dosya + S3).
293
- *
294
- * Varsayılan her şey kapalı: stdout ve admin ring mevcut davranışını korur.
295
- * `kinds` hangi structured kayıtların sink'lere gideceğini seçer; `console`
296
- * runtime `http`/`event`/`error` satırlarının terminalde görünmesini kontrol
297
- * eder (banner/build satırlarına dokunmaz).
298
- *
299
- * S3 credential'ları config'e yazılmaz — `JSKELET_S3_ACCESS_KEY_ID` /
300
- * `JSKELET_S3_SECRET_ACCESS_KEY` (ve isteğe bağlı `JSKELET_S3_SESSION_TOKEN`).
301
- * Bucket için `JSKELET_LOG_BUCKET` (veya `JSKELET_S3_BUCKET`) env'i config'i
302
- * ezer ve `ayberkenis/jskelet/logs` gibi `bucket/prefix` yolunu kabul eder.
303
- * Uyumlu API adresi `JSKELET_S3_API_URL` (S3-compatible endpoint).
304
- */
305
- export const DEFAULT_LOGS = {
306
- console: true,
307
- /** @type {Array<"http" | "event" | "error">} */
308
- kinds: ["http", "event", "error"],
309
- file: {
310
- enabled: false,
311
- /** Proje köküne göre relative. */
312
- dir: "logs",
313
- /**
314
- * Artık kullanılmıyor. Parçalar en fazla 5 dakika durur; alan çözülen
315
- * config'te durur ki eski okuyucular kırılmasın.
316
- */
317
- rotate: /** @type {"daily"} */ ("daily"),
318
- },
319
- s3: {
320
- enabled: false,
321
- /** @type {string | null} */
322
- bucket: null,
323
- prefix: "jskelet/logs/",
324
- /** @type {string | null} */
325
- region: null,
326
- /** MinIO vb. için; null → AWS. */
327
- /** @type {string | null} */
328
- endpoint: null,
329
- flushIntervalMs: 5000,
330
- maxBatch: 100,
331
- },
332
- };
333
-
334
- /**
335
- * Framework yönetim paneli (`/_jskelet/admin`).
336
- *
337
- * `enabled` varsayılan olarak **kapalı** ve ortama bakmaz: panel açıldığında
338
- * production'da da çalışır, ama açılması bilinçli bir karar olmalı. Kapalıyken
339
- * router hiç mount edilmez — yolun kendisi de yok, yani 404 dönen bir uç bile
340
- * ortaya çıkmaz.
341
- *
342
- * Şifre her süreç başlangıcında yeniden üretilir (bkz. `src/server/admin/`):
343
- * panelin ömrü sürecin ömrü kadardır ve bir deploy eski erişimi otomatik
344
- * olarak iptal eder. Bu yüzden config'te şifre alanı yok.
345
- */
346
- export const DEFAULT_ADMIN = {
347
- enabled: false,
348
- basePath: "/_jskelet/admin",
349
- /**
350
- * Boş dizi = IP kısıtı yok. Exact IP veya CIDR (`10.0.0.0/8`); listede
351
- * olmayan her istek 404 (login dahil).
352
- * @type {string[]}
353
- */
354
- allowIps: [],
355
- /** Bilinen crawler / bot UA'ları 404 ile reddedilsin mi. */
356
- blockBots: true,
357
- /** Kaç başarısız denemeden sonra IP yasaklanır. */
358
- banAttempts: 3,
359
- /** Yasağın süresi. */
360
- banHours: 24,
361
- /** Oturumun ömrü; süreç yeniden başladığında zaten sıfırlanır. */
362
- sessionHours: 12,
363
- /** Canlı log ring boyutu (HTTP + framework olayları). */
364
- logSize: 500,
365
- };
366
-
367
- /**
368
- * Cloudflare cache yüzeyi.
369
- *
370
- * JSkelet'in önbelleği origin önbelleği; ziyaretçinin gördüğü kopya CDN'de.
371
- * Bu bölüm ikisini aynı panelden yönetilebilir kılar (bkz.
372
- * `src/server/cloudflare.js`).
373
- *
374
- * Token **config'e yazılmamalı**: `JSKELET_CLOUDFLARE_KEY` env'i önceliklidir
375
- * ve önerilen yol odur. Zone kimliği sır değil, ama o da env'den okunabilir
376
- * (`JSKELET_CLOUDFLARE_ZONE_ID`).
377
- *
378
- * Gereken token izinleri: purge için `Zone.Cache Purge`, ayarlar için
379
- * `Zone.Zone Settings`, analitik için `Zone.Analytics` (salt okunur).
380
- */
381
- export const DEFAULT_CLOUDFLARE = {
382
- /** `false` verilirse env'de token olsa bile yüzey kapalı kalır. */
383
- enabled: true,
384
- /** @type {string | null} */
385
- zoneId: null,
386
- /** @type {string | null} Env tercih edilir; burada tutmak sırrı repoya sokar. */
387
- apiToken: null,
388
- /**
389
- * Purge, tam URL istiyor; panel elinde yalnızca yol tutuyor. Site adı
390
- * verilmezse purge isteğinin geldiği istek origin'i kullanılır.
391
- * @type {string | null}
392
- */
393
- hostname: null,
394
- /** Analitik penceresi (saat). Cloudflare'in izin verdiği aralıkla sınırlı. */
395
- analyticsHours: 24,
396
- };
397
-
398
- /** Oturuma bağlı sayfalar ısıtılmaz; uygulama kendi listesini verebilir. */
399
- export const DEFAULT_PREWARM_SKIP = [
400
- "/api/",
401
- "/_fragment/",
402
- "/__jskelet/",
403
- "/_jskelet/",
404
- ];
405
-
406
- /**
407
- * Site içi gezinme ipuçları (Speculation Rules + view transition).
408
- *
409
- * Varsayılan bilinçli olarak ölçülü: `prefetch` açık, çünkü yalnızca belgeyi
410
- * indirir ve sayfanın JS'ini çalıştırmaz — yanlış tahmin edilse bile tek
411
- * maliyeti bir istektir. `prerender` kapalı, çünkü hedef sayfanın script'leri
412
- * gerçekten çalışır; ölçüm kodunu `prerenderingchange` olayına bağlamayan bir
413
- * uygulamada ziyaret sayıları şişer. `viewTransition` kapalı, çünkü uygulamanın
414
- * kendi geçiş animasyonlarıyla çakışabilir.
415
- */
416
- export const DEFAULT_NAVIGATION = {
417
- /** `false` ya da eagerness: "conservative" | "moderate" | "eager". */
418
- prefetch: /** @type {false | "conservative" | "moderate" | "eager"} */ ("moderate"),
419
- /** @type {false | "conservative" | "moderate" | "eager"} */
420
- prerender: false,
421
- /** `@view-transition { navigation: auto }` basılsın mı. */
422
- viewTransition: false,
423
- };
424
-
425
- /**
426
- * Hiçbir koşulda önden getirilmeyecek yollar. Yan etkisi olan ya da gezinme
427
- * hedefi olmayan uçların spekülatif istekle tetiklenmesi gerçek bir hata
428
- * kaynağı; uygulama kendi listesini `navigation.exclude` ile ekler.
429
- */
430
- export const DEFAULT_NAVIGATION_EXCLUDE = ["/api/*", "/_fragment/*", "/_jskelet/*"];
431
-
432
- /**
433
- * Güvenlik ayarları.
434
- *
435
- * `trustProxy` varsayılan olarak açık, çünkü JSkelet uygulamaları neredeyse
436
- * her zaman bir ters proxy arkasında koşuyor ve doğru protokol/IP buna bağlı.
437
- * Ama doğrudan internete açık bir sunucuda bu, istemcinin `X-Forwarded-For`
438
- * uydurabilmesi demek — rate limit ve audit log girdiğinde kapatılmalı.
439
- *
440
- * `csrf.enabled` açık: framework gövdeyi kendisi ayrıştırdığı için bu yüzey
441
- * onun sorumluluğu. Kontrol yalnızca **çapraz site olduğu belli** istekleri
442
- * reddeder (`Origin` uyuşmuyor ya da `Sec-Fetch-Site: cross-site`); başlık
443
- * hiç yoksa geçer, böylece webhook ve sunucudan sunucuya çağrılar bozulmaz.
444
- *
445
- * `csrf.token` kapalı: çift gönderim token'ı `Origin` göndermeyen eski
446
- * tarayıcılar için ikinci katman ve formlara `csrfField()` eklenmesini
447
- * gerektiriyor, yani açılması bilinçli bir karar olmalı.
448
- */
449
- export const DEFAULT_SECURITY = {
450
- trustProxy: true,
451
- /** @type {string | null} */
452
- cookieSecret: null,
453
- csrf: {
454
- enabled: true,
455
- token: false,
456
- /** Ek olarak kabul edilen origin'ler (ör. ayrı bir admin alan adı). */
457
- allowedOrigins: /** @type {string[]} */ ([]),
458
- /** Kontrolden muaf yollar — webhook uçları buraya yazılır. */
459
- exclude: /** @type {string[]} */ ([]),
460
- cookieName: "csrf_token",
461
- fieldName: "_csrf",
462
- headerName: "x-csrf-token",
463
- },
464
- };
465
-
466
- /**
467
- * Build-zamanı görsel adımı + opsiyonel runtime uzak görsel proxy.
468
- *
469
- * `remote` kapalıyken `image()` uzak URL'leri olduğu gibi basar (eski davranış).
470
- * Açılınca yalnızca `allowHosts` listesindeki host'lar proxy edilir — boş liste
471
- * açık proxy / SSRF kapısı olurdu, bu yüzden allowHosts olmadan remote hiç
472
- * mount edilmez.
473
- */
474
- export const DEFAULT_IMAGES = {
475
- widths: [400, 640, 960, 1280, 1920],
476
- quality: 78,
477
- skip: /** @type {string[]} */ ([]),
478
- remote: {
479
- /** @type {string[]} */
480
- allowHosts: [],
481
- path: "/_jskelet/image",
482
- /** İzin verilen en büyük `w` (retina üstü israf). */
483
- maxWidth: 1920,
484
- /** Disk önbelleği Cache-Control max-age (saniye). */
485
- cacheMaxAge: 60 * 60 * 24 * 30,
486
- fetchTimeoutMs: 10_000,
487
- /** Upstream gövde üst sınırı; aşılırsa 502. */
488
- maxBytes: 10 * 1024 * 1024,
489
- },
490
- };
491
-
492
- /**
493
- * Markalama. Header adı ve dev overlay yolu tek yerden değişsin diye
494
- * config'ten okunur — fork eden proje kendi adını verebilir.
495
- */
496
- export const DEFAULT_BRAND = {
497
- name: "JSkelet",
498
- /** `<html lang>`; uygulama kendi dilini config'te bildirir. */
499
- lang: "en",
500
- poweredBy: "JSkelet",
501
- cacheHeader: "X-JSkelet-Cache",
502
- devBasePath: "/__jskelet/dev",
503
- prewarmUserAgent: "jskelet-prewarm",
504
- devTokenCookie: "dev_token",
505
- /**
506
- * Paylaşımlı cookie Domain kökleri (örn. `.investvio.com`, `.localhost`).
507
- * `writeSharedCookie` host bunlardan birine uyuyorsa Domain olarak yazar.
508
- * @type {string[]}
509
- */
510
- sharedCookieRoots: [],
511
- };
512
-
513
- /**
514
- * Kimlik / alt alan handoff. Framework oturum vermez; yalnızca çapraz-subdomain
515
- * cookie köprüsü opsiyoneldir.
516
- */
517
- export const DEFAULT_AUTH = {
518
- /**
519
- * `true` veya `{ allowedCookieNames, ttlSeconds?, path?, maxValueBytes?,
520
- * maxPendingTickets?, maxMintsPerIpPerMinute? }`.
521
- * Açıkken `POST /_jskelet/auth/handoff` ve `?handoff=` redeem middleware'i.
522
- * Mint için `allowedCookieNames` dolu olmalı (aksi halde 400).
523
- * @type {boolean | {
524
- * enabled?: boolean,
525
- * allowedCookieNames?: string[],
526
- * ttlSeconds?: number,
527
- * path?: string,
528
- * maxValueBytes?: number,
529
- * maxPendingTickets?: number,
530
- * maxMintsPerIpPerMinute?: number,
531
- * }}
532
- */
533
- crossSubdomainHandoff: false,
534
- };
1
+ /**
2
+ * Framework varsayılanları.
3
+ *
4
+ * Buradaki hiçbir değer domain bilgisi taşımaz: uygulamaya özel her şey
5
+ * `jskelet.config.mjs` üzerinden gelir. Varsayılanların ayrı dosyada olması,
6
+ * "framework ne yapıyor, uygulama ne ekliyor" ayrımını okunur tutar.
7
+ */
8
+
9
+ /** Uzun süre cache'lenecek hash'li çıktı dizini. */
10
+ export const IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
11
+
12
+ /**
13
+ * Proje kökündeki dizin adları. `jskelet.config.mjs` içinde `paths` ile
14
+ * tek tek ezilebilir.
15
+ */
16
+ export const DEFAULT_DIRS = {
17
+ /** Layout + sayfalar + bileşenler (klasik kök). */
18
+ views: "views",
19
+ /** Feature-first dikey dilimler (`features/<name>/{server,views,client}`). */
20
+ features: "features",
21
+ /** Özellikler arası paylaşılan server/views/client. */
22
+ shared: "shared",
23
+ /** Statik dosyalar; build çıktısı da buraya yazılır. */
24
+ public: "public",
25
+ /** Island runtime ve entry'ler. */
26
+ client: "client",
27
+ /** Route modülleri. */
28
+ routes: "routes",
29
+ /** Tailwind/PostCSS giriş dosyası. */
30
+ styles: "styles/globals.css",
31
+ /** Build ara çıktıları (manifest, metafile, images, templates). */
32
+ generated: ".jskelet",
33
+ };
34
+
35
+ /**
36
+ * Uzantı ve önek bazlı statik dosya tespiti. Bu listeye uyan yollara
37
+ * `IMMUTABLE_CACHE` yazılır.
38
+ */
39
+ export const DEFAULT_STATIC = {
40
+ extensions: [".svg", ".png", ".webp", ".avif", ".ico", ".woff2"],
41
+ prefixes: ["/assets/", "/fonts/"],
42
+ };
43
+
44
+ /** Dev gate'in hiçbir koşulda kapatmadığı yollar. */
45
+ export const DEFAULT_DEV_GATE_BYPASS = [
46
+ "/api/healthcheck",
47
+ "/robots.txt",
48
+ "/sitemap.xml",
49
+ "/site.webmanifest",
50
+ "/favicon.ico",
51
+ ];
52
+
53
+ /**
54
+ * Klasik (liste tabanlı) prewarm ayarları. `onVisit` modu bunlarla birlikte
55
+ * kullanılamaz — ya açılış/`prewarmPaths` turu, ya ziyaret edilen sayfadaki
56
+ * linkleri ısıtma.
57
+ */
58
+ export const DEFAULT_PREWARM = {
59
+ enabled: true,
60
+ max: 400,
61
+ intervalSeconds: 0,
62
+ /** Paralel ısıtma isteği; dev'de sunucu tek süreç olduğu için düşürülür. */
63
+ concurrency: 4,
64
+ /** İki tur arasında beklenen süre: upstream'e ani yük binmesin. */
65
+ delayMs: 0,
66
+ /**
67
+ * Saniyedeki en fazla ısıtma isteği. 0 → sınırsız (yalnızca `concurrency`
68
+ * frenler). Upstream'i kota sınırının altında tutmanın en doğrudan yolu bu:
69
+ * paralellik ne kadar yükselse de tur bu hızın üstüne çıkmaz.
70
+ */
71
+ rps: 0,
72
+ /**
73
+ * Tekrar turundan önce beklenen süre. Rate limit pencereleri saniye
74
+ * mertebesinde; hemen tekrar denemek aynı 429'u almak demek.
75
+ */
76
+ retryDelayMs: 2000,
77
+ /**
78
+ * Liste `max`'tan uzunsa periyodik turlar kaldığı yerden devam eder.
79
+ * Böylece 10.000 yolluk bir site tek turda değil, turlar boyunca ısınır.
80
+ * `priority` eşleşen yollar her turda ısıtıldığı için rotasyon yalnızca
81
+ * kuyruğu dolaşır.
82
+ */
83
+ rotate: true,
84
+ /**
85
+ * Isıtma sırasını belirleyen desenler. String (`/haber/:slug`) ya da
86
+ * `RegExp` kabul eder; önce yazılan önce ısınır.
87
+ * @type {(string | RegExp)[]}
88
+ */
89
+ priority: [],
90
+ /**
91
+ * Klasik turda ısıtılacak origin listesi. Boşsa `http://127.0.0.1:<port>`.
92
+ * `cache().vary.host` açıkken locale host'ları buraya yazılmazsa yalnızca
93
+ * loopback anahtarı ısınır.
94
+ * @type {string[]}
95
+ */
96
+ origins: [],
97
+ };
98
+
99
+ /**
100
+ * Ziyaret tabanlı ısıtma. Bir sayfa servis edilince HTML'deki aynı-origin
101
+ * linkler kuyruğa alınır; bir sonraki tıklama (veya başka ziyaretçi) çoğu
102
+ * zaman HIT görür. Klasik `prewarm` alanlarıyla karşılıklı dışlayıcıdır.
103
+ */
104
+ export const DEFAULT_PREWARM_ON_VISIT = {
105
+ enabled: false,
106
+ /** Sayfa başına üstten alta en fazla kaç link kuyruğa alınır. */
107
+ perPage: 20,
108
+ /**
109
+ * Paralel işçi. `null` yalnızca kapalı modda durur; açıkken tavan
110
+ * (`ON_VISIT_CONCURRENCY_CEILING`) uygulanır. Klasik prewarm'daki
111
+ * prod 4 burada geçerli değil — onVisit sürekli çalışır.
112
+ * @type {number | null}
113
+ */
114
+ concurrency: null,
115
+ /**
116
+ * Saniyedeki istek tavanı. `null` yalnızca kapalı modda durur; açıkken
117
+ * `0` (sınırsız) dahil her şey `ON_VISIT_RPS_CEILING` ile kesilir.
118
+ * @type {number | null}
119
+ */
120
+ rps: null,
121
+ };
122
+
123
+ /**
124
+ * onVisit sürekli tur olduğu için klasik prewarm'daki "sınırsız" burada yok.
125
+ * Config daha yükseğini yazsa da çözümlenen değer bu tavanları geçemez.
126
+ */
127
+ export const ON_VISIT_PER_PAGE_CEILING = 20;
128
+ export const ON_VISIT_RPS_CEILING = 2;
129
+ export const ON_VISIT_CONCURRENCY_CEILING = 2;
130
+
131
+ /**
132
+ * Bekleyen onVisit yolları. Bir sayfa düzinelerce link basınca kuyruk
133
+ * birikmesin; taşan link bu turda alınmaz.
134
+ */
135
+ export const ON_VISIT_QUEUE_MAX = 64;
136
+
137
+ /** `onVisit` açıkken `cache().prewarm` kökünde yasak olan klasik alanlar. */
138
+ export const CLASSIC_PREWARM_KEYS = [
139
+ "enabled",
140
+ "max",
141
+ "intervalSeconds",
142
+ "concurrency",
143
+ "delayMs",
144
+ "rps",
145
+ "retryDelayMs",
146
+ "rotate",
147
+ "priority",
148
+ "origins",
149
+ ];
150
+
151
+ /**
152
+ * HTML önbelleğinin girdi sınırı. 500 girdi ortalama bir sayfa boyutunda
153
+ * yaklaşık 100-200 MB tutar; uzun kuyruklu siteler bunu yükseltmek yerine
154
+ * veri önbelleğine yaslanmalı (bkz. `DEFAULT_DATA_CACHE`).
155
+ *
156
+ * Config daha yükseğini istese de girdi sayısı `HTML_CACHE_MAX_ENTRIES_CEILING`
157
+ * değerini geçemez. Asıl bellek freni bayt bütçesidir: şişman sayfa ve
158
+ * `vary.host` kopyası sayı tavanının altında da RSS'i şişirir.
159
+ */
160
+ export const DEFAULT_HTML_CACHE_MAX_ENTRIES = 500;
161
+
162
+ /**
163
+ * Edge taze penceresi bittikten sonra eski HTML'in sunulacağı süre (saniye).
164
+ * `cache().staleWhileRevalidate`. 0 ise `stale-while-revalidate` direktifi
165
+ * basılmaz. Süreç içi HTML cache'in stale penceresinden bağımsızdır.
166
+ */
167
+ export const DEFAULT_STALE_WHILE_REVALIDATE = 60;
168
+
169
+ /** `cache().maxEntries` için sert tavan. Üstü uyarıyla bu değere çekilir. */
170
+ export const HTML_CACHE_MAX_ENTRIES_CEILING = 800;
171
+
172
+ /**
173
+ * Süreç içi HTML string + sıkıştırılmış gövde tavanı (256 MB).
174
+ * `install()` bunu uygular; config yükseltemez. Tek sayfa bütçeden büyükse
175
+ * saklanmaz, yanıt yine gider.
176
+ */
177
+ export const HTML_CACHE_BYTE_BUDGET = 256 * 1024 * 1024;
178
+
179
+ /**
180
+ * Süreç içi veri önbelleğinin JSON bayt tavanı (64 MB). Sayı tavanı
181
+ * şişman gövdeleri tutmaz; config yükseltemez. Tek değer bütçeden büyükse
182
+ * saklanmaz, çağıran sonucu yine alır.
183
+ */
184
+ export const DATA_CACHE_BYTE_BUDGET = 64 * 1024 * 1024;
185
+
186
+ /**
187
+ * Uzak görsel disk önbelleğinin tavanı (256 MB). `.jskelet/image-cache/`
188
+ * bu boyutu aşınca en eski dosya düşer. Config yükseltemez.
189
+ */
190
+ export const IMAGE_CACHE_BYTE_BUDGET = 256 * 1024 * 1024;
191
+
192
+ /** `cache().data.maxEntries` için sert tavan. Uzun kuyruk burada durur, HTML'de değil. */
193
+ export const DATA_CACHE_MAX_ENTRIES_CEILING = 20_000;
194
+
195
+ /**
196
+ * `notFound()` geçici bir upstream hatasına denk geldiğinde sayfanın kaç kez
197
+ * daha denenmesi gerektiği.
198
+ *
199
+ * Varsayılan tek deneme: maliyeti upstream'e binen ikinci bir istek turu, ama
200
+ * alternatifi var olan bir sayfayı 404 olarak servis etmek — arama motoru için
201
+ * geçici bir rate limit'in kalıcı kayba dönüşmesi. `attempts: 0` tekrarı
202
+ * kapatır ve doğrudan önbelleğe girmeyen 503'e düşer.
203
+ */
204
+ export const DEFAULT_TRANSIENT_RETRY = {
205
+ attempts: 1,
206
+ delayMs: 300,
207
+ };
208
+
209
+ /**
210
+ * Upstream veri önbelleği.
211
+ *
212
+ * HTML önbelleğinden bilinçli olarak çok daha büyük: JSON, aynı sayfanın
213
+ * HTML'ine göre onlarca kat küçük. Uzun kuyruğu (on binlerce haber/etiket)
214
+ * HTML olarak tutmak imkânsız, verisini tutmak ise ucuz — ve API kotasını
215
+ * koruyan katman burası.
216
+ */
217
+ export const DEFAULT_DATA_CACHE = {
218
+ maxEntries: 10000,
219
+ /**
220
+ * TTL dolduktan sonra girdinin kaç TTL boyunca daha kullanılabileceği.
221
+ * HTML'deki 1 katsayısından yüksek: bayat veri, eksik sayfadan iyidir ve
222
+ * upstream düştüğünde tek elde kalan şey budur.
223
+ */
224
+ staleFactor: 10,
225
+ };
226
+
227
+ /**
228
+ * Upstream API'ye giden isteklerin host başına hız freni.
229
+ *
230
+ * Varsayılan **kapalı** (`rate: 0`): fren, kotasını bilen bir uygulamanın
231
+ * bilinçli kararı. Açıldığında `rate` bir tavan olur ve gerçek hız 429
232
+ * cevaplarına göre kendini aşağı çeker (bkz. `src/server/upstream-limiter.js`).
233
+ *
234
+ * `hosts` ile tek tek uçlar ayrılabilir; API'lerin kotası aynı olmak zorunda
235
+ * değil:
236
+ *
237
+ * upstream: { rate: 10, hosts: { "api.example.com": { rate: 3 } } }
238
+ */
239
+ export const DEFAULT_UPSTREAM_LIMIT = {
240
+ /** Saniyedeki en fazla çağrı. 0 → fren tamamen kapalı. */
241
+ rate: 0,
242
+ /** Kova boyu; verilmezse bir saniyelik bütçe kadar patlamaya izin verilir. */
243
+ burst: 0,
244
+ /** Aynı anda uçabilecek çağrı. Ortalama hızdan bağımsız: anlık baskıyı bağlar. */
245
+ concurrency: 8,
246
+ /** Azalmanın dibi: hız buranın altına inmez, yoksa site tamamen durur. */
247
+ minRate: 0.5,
248
+ /** Toplamsal artışın adımı (çağrı/saniye) ve periyodu. */
249
+ increaseStep: 1,
250
+ increaseIntervalMs: 5000,
251
+ /**
252
+ * İki azalma arasındaki en kısa süre. Aynı anda uçan on çağrının hepsi 429
253
+ * dönerse hız on kez yarılanıp dibe vurmasın.
254
+ */
255
+ decreaseIntervalMs: 1000,
256
+ /** Devre kesici: art arda kaç 429'dan sonra host'a hiç gidilmeyeceği. */
257
+ breakerFailures: 5,
258
+ breakerCooldownMs: 10_000,
259
+ /** @type {Record<string, Record<string, number>>} */
260
+ hosts: {},
261
+ };
262
+
263
+ /**
264
+ * Opsiyonel Redis ikinci kademesi (L2).
265
+ *
266
+ * Redis **birincil store değil**: bellek içi önbellek (L1) aynen kalır, Redis
267
+ * iki iş yapar — L1'de bulunmayan bir sayfa için render'ı atlatmak ve
268
+ * invalidation'ı bütün node'lara yaymak. Tek instance çalışan bir kurulumda
269
+ * kazanç neredeyse yok; bu yüzden `enabled` varsayılan olarak kapalı.
270
+ *
271
+ * `storeEncoded` kapalı, çünkü sıkıştırılmış gövdeleri de paylaşmak girdi
272
+ * başına boyutu iki-üç katına çıkarır ve brotli'yi yeniden üretmek Redis'ten
273
+ * indirmekten çoğu zaman daha ucuz.
274
+ */
275
+ export const DEFAULT_REDIS = {
276
+ enabled: false,
277
+ /** `redis://` ya da `rediss://`. Boşsa ioredis varsayılanı (localhost:6379). */
278
+ url: /** @type {string | null} */ (null),
279
+ /** Aynı Redis'i paylaşan birden fazla uygulamayı ayırır. */
280
+ namespace: "default",
281
+ keyPrefix: "_jskelet",
282
+ /** HTML gövdeleri paylaşılsın mı. */
283
+ html: true,
284
+ /** Veri önbelleği paylaşılsın mı. */
285
+ data: true,
286
+ /** Brotli/gzip gövdeleri de paylaşılsın mı. */
287
+ storeEncoded: false,
288
+ /** pub/sub üzerinden invalidation yayını. */
289
+ events: true,
290
+ /**
291
+ * Tek bir komutun en fazla bekletebileceği süre. Önbellek okuması isteği
292
+ * bloklayan bir adım: Redis takıldığında render'a düşmek, ağı beklemekten
293
+ * iyidir.
294
+ */
295
+ commandTimeoutMs: 200,
296
+ };
297
+
298
+ /**
299
+ * Kalıcı log sink'leri (dosya + S3).
300
+ *
301
+ * Varsayılan her şey kapalı: stdout ve admin ring mevcut davranışını korur.
302
+ * `kinds` hangi structured kayıtların sink'lere gideceğini seçer; `console`
303
+ * runtime `http`/`event`/`error` satırlarının terminalde görünmesini kontrol
304
+ * eder (banner/build satırlarına dokunmaz).
305
+ *
306
+ * S3 credential'ları config'e yazılmaz — `JSKELET_S3_ACCESS_KEY_ID` /
307
+ * `JSKELET_S3_SECRET_ACCESS_KEY` (ve isteğe bağlı `JSKELET_S3_SESSION_TOKEN`).
308
+ * Bucket için `JSKELET_LOG_BUCKET` (veya `JSKELET_S3_BUCKET`) env'i config'i
309
+ * ezer ve `ayberkenis/jskelet/logs` gibi `bucket/prefix` yolunu kabul eder.
310
+ * Uyumlu API adresi `JSKELET_S3_API_URL` (S3-compatible endpoint).
311
+ */
312
+ export const DEFAULT_LOGS = {
313
+ console: true,
314
+ /** @type {Array<"http" | "event" | "error">} */
315
+ kinds: ["http", "event", "error"],
316
+ file: {
317
+ enabled: false,
318
+ /** Proje köküne göre relative. */
319
+ dir: "logs",
320
+ /**
321
+ * Artık kullanılmıyor. Parçalar en fazla 5 dakika durur; alan çözülen
322
+ * config'te durur ki eski okuyucular kırılmasın.
323
+ */
324
+ rotate: /** @type {"daily"} */ ("daily"),
325
+ },
326
+ s3: {
327
+ enabled: false,
328
+ /** @type {string | null} */
329
+ bucket: null,
330
+ prefix: "jskelet/logs/",
331
+ /** @type {string | null} */
332
+ region: null,
333
+ /** MinIO vb. için; null → AWS. */
334
+ /** @type {string | null} */
335
+ endpoint: null,
336
+ flushIntervalMs: 5000,
337
+ maxBatch: 100,
338
+ },
339
+ };
340
+
341
+ /**
342
+ * Framework yönetim paneli (`/_jskelet/admin`).
343
+ *
344
+ * `enabled` varsayılan olarak **kapalı** ve ortama bakmaz: panel açıldığında
345
+ * production'da da çalışır, ama açılması bilinçli bir karar olmalı. Kapalıyken
346
+ * router hiç mount edilmez — yolun kendisi de yok, yani 404 dönen bir uç bile
347
+ * ortaya çıkmaz.
348
+ *
349
+ * Şifre her süreç başlangıcında yeniden üretilir (bkz. `src/server/admin/`):
350
+ * panelin ömrü sürecin ömrü kadardır ve bir deploy eski erişimi otomatik
351
+ * olarak iptal eder. Bu yüzden config'te şifre alanı yok.
352
+ */
353
+ export const DEFAULT_ADMIN = {
354
+ enabled: false,
355
+ basePath: "/_jskelet/admin",
356
+ /**
357
+ * Boş dizi = IP kısıtı yok. Exact IP veya CIDR (`10.0.0.0/8`); listede
358
+ * olmayan her istek 404 (login dahil).
359
+ * @type {string[]}
360
+ */
361
+ allowIps: [],
362
+ /** Bilinen crawler / bot UA'ları 404 ile reddedilsin mi. */
363
+ blockBots: true,
364
+ /** Kaç başarısız denemeden sonra IP yasaklanır. */
365
+ banAttempts: 3,
366
+ /** Yasağın süresi. */
367
+ banHours: 24,
368
+ /** Oturumun ömrü; süreç yeniden başladığında zaten sıfırlanır. */
369
+ sessionHours: 12,
370
+ /** Canlı log ring boyutu (HTTP + framework olayları). */
371
+ logSize: 500,
372
+ };
373
+
374
+ /**
375
+ * Cloudflare cache yüzeyi.
376
+ *
377
+ * JSkelet'in önbelleği origin önbelleği; ziyaretçinin gördüğü kopya CDN'de.
378
+ * Bu bölüm ikisini aynı panelden yönetilebilir kılar (bkz.
379
+ * `src/server/cloudflare.js`).
380
+ *
381
+ * Token **config'e yazılmamalı**: `JSKELET_CLOUDFLARE_KEY` env'i önceliklidir
382
+ * ve önerilen yol odur. Zone kimliği sır değil, ama o da env'den okunabilir
383
+ * (`JSKELET_CLOUDFLARE_ZONE_ID`).
384
+ *
385
+ * Gereken token izinleri: purge için `Zone.Cache Purge`, ayarlar için
386
+ * `Zone.Zone Settings`, analitik için `Zone.Analytics` (salt okunur).
387
+ */
388
+ export const DEFAULT_CLOUDFLARE = {
389
+ /** `false` verilirse env'de token olsa bile yüzey kapalı kalır. */
390
+ enabled: true,
391
+ /** @type {string | null} */
392
+ zoneId: null,
393
+ /** @type {string | null} Env tercih edilir; burada tutmak sırrı repoya sokar. */
394
+ apiToken: null,
395
+ /**
396
+ * Purge, tam URL istiyor; panel elinde yalnızca yol tutuyor. Site adı
397
+ * verilmezse purge isteğinin geldiği istek origin'i kullanılır.
398
+ * @type {string | null}
399
+ */
400
+ hostname: null,
401
+ /** Analitik penceresi (saat). Cloudflare'in izin verdiği aralıkla sınırlı. */
402
+ analyticsHours: 24,
403
+ };
404
+
405
+ /** Oturuma bağlı sayfalar ısıtılmaz; uygulama kendi listesini verebilir. */
406
+ export const DEFAULT_PREWARM_SKIP = [
407
+ "/api/",
408
+ "/_fragment/",
409
+ "/__jskelet/",
410
+ "/_jskelet/",
411
+ ];
412
+
413
+ /**
414
+ * Site içi gezinme ipuçları (Speculation Rules + view transition).
415
+ *
416
+ * Varsayılan bilinçli olarak ölçülü: `prefetch` açık, çünkü yalnızca belgeyi
417
+ * indirir ve sayfanın JS'ini çalıştırmaz — yanlış tahmin edilse bile tek
418
+ * maliyeti bir istektir. `prerender` kapalı, çünkü hedef sayfanın script'leri
419
+ * gerçekten çalışır; ölçüm kodunu `prerenderingchange` olayına bağlamayan bir
420
+ * uygulamada ziyaret sayıları şişer. `viewTransition` kapalı, çünkü uygulamanın
421
+ * kendi geçiş animasyonlarıyla çakışabilir.
422
+ */
423
+ export const DEFAULT_NAVIGATION = {
424
+ /** `false` ya da eagerness: "conservative" | "moderate" | "eager". */
425
+ prefetch: /** @type {false | "conservative" | "moderate" | "eager"} */ ("moderate"),
426
+ /** @type {false | "conservative" | "moderate" | "eager"} */
427
+ prerender: false,
428
+ /** `@view-transition { navigation: auto }` basılsın mı. */
429
+ viewTransition: false,
430
+ };
431
+
432
+ /**
433
+ * Hiçbir koşulda önden getirilmeyecek yollar. Yan etkisi olan ya da gezinme
434
+ * hedefi olmayan uçların spekülatif istekle tetiklenmesi gerçek bir hata
435
+ * kaynağı; uygulama kendi listesini `navigation.exclude` ile ekler.
436
+ */
437
+ export const DEFAULT_NAVIGATION_EXCLUDE = ["/api/*", "/_fragment/*", "/_jskelet/*"];
438
+
439
+ /**
440
+ * Güvenlik ayarları.
441
+ *
442
+ * `trustProxy` varsayılan olarak açık, çünkü JSkelet uygulamaları neredeyse
443
+ * her zaman bir ters proxy arkasında koşuyor ve doğru protokol/IP buna bağlı.
444
+ * Ama doğrudan internete açık bir sunucuda bu, istemcinin `X-Forwarded-For`
445
+ * uydurabilmesi demek — rate limit ve audit log girdiğinde kapatılmalı.
446
+ *
447
+ * `csrf.enabled` açık: framework gövdeyi kendisi ayrıştırdığı için bu yüzey
448
+ * onun sorumluluğu. Kontrol yalnızca **çapraz site olduğu belli** istekleri
449
+ * reddeder (`Origin` uyuşmuyor ya da `Sec-Fetch-Site: cross-site`); başlık
450
+ * hiç yoksa geçer, böylece webhook ve sunucudan sunucuya çağrılar bozulmaz.
451
+ *
452
+ * `csrf.token` kapalı: çift gönderim token'ı `Origin` göndermeyen eski
453
+ * tarayıcılar için ikinci katman ve formlara `csrfField()` eklenmesini
454
+ * gerektiriyor, yani açılması bilinçli bir karar olmalı.
455
+ */
456
+ export const DEFAULT_SECURITY = {
457
+ trustProxy: true,
458
+ /** @type {string | null} */
459
+ cookieSecret: null,
460
+ csrf: {
461
+ enabled: true,
462
+ token: false,
463
+ /** Ek olarak kabul edilen origin'ler (ör. ayrı bir admin alan adı). */
464
+ allowedOrigins: /** @type {string[]} */ ([]),
465
+ /** Kontrolden muaf yollar — webhook uçları buraya yazılır. */
466
+ exclude: /** @type {string[]} */ ([]),
467
+ cookieName: "csrf_token",
468
+ fieldName: "_csrf",
469
+ headerName: "x-csrf-token",
470
+ },
471
+ };
472
+
473
+ /**
474
+ * Build-zamanı görsel adımı + opsiyonel runtime uzak görsel proxy.
475
+ *
476
+ * `remote` kapalıyken `image()` uzak URL'leri olduğu gibi basar (eski davranış).
477
+ * Açılınca yalnızca `allowHosts` listesindeki host'lar proxy edilir — boş liste
478
+ * açık proxy / SSRF kapısı olurdu, bu yüzden allowHosts olmadan remote hiç
479
+ * mount edilmez.
480
+ */
481
+ export const DEFAULT_IMAGES = {
482
+ widths: [400, 640, 960, 1280, 1920],
483
+ quality: 78,
484
+ skip: /** @type {string[]} */ ([]),
485
+ remote: {
486
+ /** @type {string[]} */
487
+ allowHosts: [],
488
+ path: "/_jskelet/image",
489
+ /** İzin verilen en büyük `w` (retina üstü israf). */
490
+ maxWidth: 1920,
491
+ /** Disk önbelleği Cache-Control max-age (saniye). */
492
+ cacheMaxAge: 60 * 60 * 24 * 30,
493
+ fetchTimeoutMs: 10_000,
494
+ /** Upstream gövde üst sınırı; aşılırsa 502. */
495
+ maxBytes: 10 * 1024 * 1024,
496
+ },
497
+ };
498
+
499
+ /**
500
+ * Markalama. Header adı ve dev overlay yolu tek yerden değişsin diye
501
+ * config'ten okunur — fork eden proje kendi adını verebilir.
502
+ */
503
+ export const DEFAULT_BRAND = {
504
+ name: "JSkelet",
505
+ /** `<html lang>`; uygulama kendi dilini config'te bildirir. */
506
+ lang: "en",
507
+ poweredBy: "JSkelet",
508
+ cacheHeader: "X-JSkelet-Cache",
509
+ devBasePath: "/__jskelet/dev",
510
+ prewarmUserAgent: "jskelet-prewarm",
511
+ devTokenCookie: "dev_token",
512
+ /**
513
+ * Paylaşımlı cookie Domain kökleri (örn. `.investvio.com`, `.localhost`).
514
+ * `writeSharedCookie` host bunlardan birine uyuyorsa Domain olarak yazar.
515
+ * @type {string[]}
516
+ */
517
+ sharedCookieRoots: [],
518
+ };
519
+
520
+ /**
521
+ * Kimlik / alt alan handoff. Framework oturum vermez; yalnızca çapraz-subdomain
522
+ * cookie köprüsü opsiyoneldir.
523
+ */
524
+ export const DEFAULT_AUTH = {
525
+ /**
526
+ * `true` veya `{ allowedCookieNames, ttlSeconds?, path?, maxValueBytes?,
527
+ * maxPendingTickets?, maxMintsPerIpPerMinute? }`.
528
+ * Açıkken `POST /_jskelet/auth/handoff` ve `?handoff=` redeem middleware'i.
529
+ * Mint için `allowedCookieNames` dolu olmalı (aksi halde 400).
530
+ * @type {boolean | {
531
+ * enabled?: boolean,
532
+ * allowedCookieNames?: string[],
533
+ * ttlSeconds?: number,
534
+ * path?: string,
535
+ * maxValueBytes?: number,
536
+ * maxPendingTickets?: number,
537
+ * maxMintsPerIpPerMinute?: number,
538
+ * }}
539
+ */
540
+ crossSubdomainHandoff: false,
541
+ };