jskelet 0.1.1 → 0.1.3
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.
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +129 -2
- package/README.md +21 -7
- package/bin/jskelet.mjs +6 -6
- package/docs/03-routing.md +48 -9
- package/docs/04-render-ve-sablonlar.md +2 -2
- package/docs/05-islands.md +59 -6
- package/docs/06-cache.md +240 -26
- package/docs/07-yapilandirma.md +108 -7
- package/docs/08-build.md +4 -4
- package/docs/09-dev-araclari.md +5 -0
- package/docs/12-panel-ve-oturum.md +384 -0
- package/docs/README.md +25 -2
- package/docs/en/01-getting-started.md +292 -0
- package/docs/en/02-architecture.md +305 -0
- package/docs/en/03-routing.md +493 -0
- package/docs/en/04-rendering.md +504 -0
- package/docs/en/05-islands.md +492 -0
- package/docs/en/06-caching.md +640 -0
- package/docs/en/07-configuration.md +789 -0
- package/docs/en/08-build.md +383 -0
- package/docs/en/09-dev-tools.md +314 -0
- package/docs/en/10-deployment.md +332 -0
- package/docs/en/11-migration.md +360 -0
- package/docs/en/12-dashboards-and-sessions.md +392 -0
- package/docs/en/README.md +112 -0
- package/package.json +4 -2
- package/src/build/build.mjs +1 -1
- package/src/build/tasks/client.mjs +2 -2
- package/src/build/tasks/fonts.mjs +3 -3
- package/src/build/tasks/icons.mjs +1 -1
- package/src/build/tasks/images.mjs +2 -2
- package/src/client/devtools/overlay.js +196 -164
- package/src/client/devtools/report.js +96 -96
- package/src/client/form.js +192 -0
- package/src/client/index.js +10 -1
- package/src/client/registry.js +78 -4
- package/src/client/swap.js +188 -0
- package/src/config/defaults.js +83 -0
- package/src/config/index.js +129 -18
- package/src/config/pattern.js +1 -1
- package/src/dev-server.mjs +1 -1
- package/src/http/control-flow.js +16 -1
- package/src/http/cookies.js +257 -0
- package/src/http/request-context.js +162 -0
- package/src/index.js +26 -2
- package/src/init.mjs +32 -31
- package/src/log.mjs +8 -2
- package/src/logo.png +0 -0
- package/src/runtime/alias-hooks.mjs +1 -1
- package/src/server/assets.js +1 -1
- package/src/server/create-app.js +12 -4
- package/src/server/data-cache.js +244 -0
- package/src/server/dev/devtools.js +6 -2
- package/src/server/dev/report.js +8 -1
- package/src/server/dev/version-check.mjs +139 -0
- package/src/server/head-hints.js +1 -1
- package/src/server/html-cache.js +32 -6
- package/src/server/middleware/csrf.js +134 -0
- package/src/server/prewarm.js +164 -19
- package/src/server/render.js +256 -20
- package/src/server/router.js +14 -7
- package/src/server/status-page.js +1 -1
- package/src/version.mjs +9 -4
- package/src/views/components/loader.js +1 -1
- package/src/views/helpers/tags.js +53 -1
package/docs/06-cache.md
CHANGED
|
@@ -4,8 +4,9 @@ Bu belge JSkelet'in ISR ikamesini bütün ayrıntılarıyla anlatır: HTML TTL
|
|
|
4
4
|
önbelleği ve stale-while-revalidate davranışı, `revalidate`'in nereden geldiği,
|
|
5
5
|
cache anahtarının nasıl kurulduğu, `X-JSkelet-Cache` başlığının değerleri,
|
|
6
6
|
sıkıştırılmış gövdenin neden önbellekte durduğu, istek içi memoizasyon
|
|
7
|
-
(`withRequestCache` / `cache()`),
|
|
8
|
-
etkilediği (`reportUpstreamFailure`) ve sunucu
|
|
7
|
+
(`withRequestCache` / `cache()`), veri önbelleği (`withDataCache`), upstream
|
|
8
|
+
hatalarının önbelleği nasıl etkilediği (`reportUpstreamFailure`) ve sunucu
|
|
9
|
+
açılışındaki ısıtma turu.
|
|
9
10
|
Kararların arkasındaki ölçüm gerekçeleri [02-mimari.md](./02-mimari.md)'de,
|
|
10
11
|
config alanlarının tam referansı [07-yapilandirma.md](./07-yapilandirma.md)'de.
|
|
11
12
|
|
|
@@ -17,12 +18,51 @@ route(controller, { revalidate })
|
|
|
17
18
|
└─ withUpstreamTracking(...) ← eksik veri tespiti
|
|
18
19
|
└─ withRequestCache(...) ← istek içi memoizasyon
|
|
19
20
|
└─ produce() → controller + renderPage
|
|
21
|
+
└─ withDataCache(...) ← upstream veri önbelleği
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
Sıra önemli: **istek içi cache en içte** olmalı ki aynı render'daki iki çağrı
|
|
23
25
|
tek upstream isteğine düşsün; **upstream takibi HTML cache'in içinde** olmalı ki
|
|
24
26
|
eksik veriyle üretilen çıktı önbelleğe yazılmasın.
|
|
25
27
|
|
|
28
|
+
İki önbelleğin iş bölümü:
|
|
29
|
+
|
|
30
|
+
| | HTML önbelleği | Veri önbelleği |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| Ne tutar | Sayfanın tamamı (+ sıkıştırılmış gövdesi) | Upstream'den gelen JSON |
|
|
33
|
+
| Girdi boyutu | ~100-200 kB | ~1-20 kB |
|
|
34
|
+
| Girdi sınırı | 500 (`cache().maxEntries`) | 10.000 (`cache().data.maxEntries`) |
|
|
35
|
+
| Kime yarar | Trafiği olan sayfalar: render bile edilmez | Uzun kuyruk: render edilir ama API'ye gidilmez |
|
|
36
|
+
|
|
37
|
+
Bu ayrım pratikte şuna karşılık gelir: on binlerce yollu bir sitede sayfaların
|
|
38
|
+
tamamını HTML olarak sıcak tutmak mümkün değil — 500 girdiyi aşan ısıtma kendi
|
|
39
|
+
ısıttığını siler. Uzun kuyruk için hedef "HTML hazır olsun" değil, **"sayfayı
|
|
40
|
+
üretecek veri API'ye gitmeden bulunsun"** olmalı. O zaman hiç ısıtılmamış bir
|
|
41
|
+
sayfa da ilk ziyaretçide milisaniyeler içinde üretilir ve kota harcamaz.
|
|
42
|
+
|
|
43
|
+
## Public ve kişiye özel ayrımı
|
|
44
|
+
|
|
45
|
+
Bu belgedeki her şey **herkese aynı gidebilen** HTML için geçerli. Cache
|
|
46
|
+
anahtarında kimlik yok (yalnızca yol + query), yani önbellekteki bir sayfa onu
|
|
47
|
+
ilk isteyen kişinin değil, o yolun cevabıdır.
|
|
48
|
+
|
|
49
|
+
Kullanıcıya bağlı bir sayfa bu yüzden ayrı bir yoldan geçer:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
app.get("/panel", route(async ({ req }) => { … }, { private: true }));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`private: true` üç şeyi birden yapar: önbellek devre dışı kalır, config'in
|
|
56
|
+
`cache.html` deseni bu kararı **ezemez** ve yanıt `private, no-store`,
|
|
57
|
+
`Vary: Cookie` ile, ETag'siz gider. Ayrıntılar ve oturum/CSRF tarafı
|
|
58
|
+
[12-panel-ve-oturum.md](./12-panel-ve-oturum.md)'de.
|
|
59
|
+
|
|
60
|
+
Bayrağı unutursanız framework sessiz kalmaz: controller `Cookie`,
|
|
61
|
+
`Authorization` ya da `req.session`/`req.user` okuduğu anda render işaretlenir
|
|
62
|
+
ve önbelleğe **yazılmaz**. Dev'de istek bir hatayla düşer, üretimde `no-store`
|
|
63
|
+
ile servis edilip loglanır. Koruma bir mazeret değil son savunma — doğru yer
|
|
64
|
+
`private: true`.
|
|
65
|
+
|
|
26
66
|
## `revalidate` — TTL nereden gelir
|
|
27
67
|
|
|
28
68
|
Bir route'un TTL'i iki kaynaktan gelebilir ve **config kazanır**:
|
|
@@ -31,6 +71,9 @@ Bir route'un TTL'i iki kaynaktan gelebilir ve **config kazanır**:
|
|
|
31
71
|
2. `jskelet.config.mjs` → `cache().html` içindeki eşleşen desen. Varsa
|
|
32
72
|
route'unkini ezer.
|
|
33
73
|
|
|
74
|
+
Tek istisna `private: true`: desen eşleşse bile yok sayılır. Kilit tek yönlü,
|
|
75
|
+
çünkü ters yönde bir hata sessiz veri sızıntısı anlamına geliyor.
|
|
76
|
+
|
|
34
77
|
```js
|
|
35
78
|
// jskelet.config.mjs
|
|
36
79
|
export default {
|
|
@@ -53,8 +96,14 @@ mümkün kılar; route dosyalarını dolaşmak gerekmez.
|
|
|
53
96
|
yapılmaz. Hiç `cache().html` kuralı yoksa doğrudan route'un değeri kullanılır.
|
|
54
97
|
|
|
55
98
|
`revalidate` verilmemişse ya da 0 ise sayfa **hiç önbelleklenmez**: her istek
|
|
56
|
-
render edilir ve
|
|
57
|
-
`X-JSkelet-Cache
|
|
99
|
+
render edilir ve yanıt `Cache-Control: private, no-store` ile, ETag'siz gider.
|
|
100
|
+
`X-JSkelet-Cache` başlığı da yazılmaz — önbellek yolu hiç çalışmadı, `MISS`
|
|
101
|
+
demek yanıltıcı olurdu.
|
|
102
|
+
|
|
103
|
+
Dinamik bir sayfaya `no-store` yazılması bilinçli. Hiç direktif taşımayan bir
|
|
104
|
+
yanıtı HTTP "sezgisel olarak önbelleklenebilir" sayıyor; araya giren bir proxy
|
|
105
|
+
ya da tarayıcının geri tuşu, tek bir ziyaretçi için üretilmiş HTML'i
|
|
106
|
+
saklayabiliyordu.
|
|
58
107
|
|
|
59
108
|
Önbellek ayrıca yalnızca `GET` istekleri için devreye girer.
|
|
60
109
|
|
|
@@ -70,8 +119,8 @@ ile `/liste?sayfa=3` ayrı girdilerdir.
|
|
|
70
119
|
Bunun pratik sonucu: query string'e bağlı olmayan bir sayfa, farklı kampanya
|
|
71
120
|
parametreleriyle (`?utm_source=…`) çağrıldığında her kombinasyon için ayrı bir
|
|
72
121
|
girdi üretir. Bu tür parametreleri ters proxy katmanında temizlemek ya da
|
|
73
|
-
önbelleği kapatmak (`revalidate` vermemek) makul bir önlemdir; store
|
|
74
|
-
500 girdi tutar ve LRU ile en eskiyi düşürür.
|
|
122
|
+
önbelleği kapatmak (`revalidate` vermemek) makul bir önlemdir; store varsayılan
|
|
123
|
+
olarak en fazla 500 girdi tutar ve LRU ile en eskiyi düşürür.
|
|
75
124
|
|
|
76
125
|
## Stale-while-revalidate
|
|
77
126
|
|
|
@@ -92,7 +141,7 @@ Okuma davranışı:
|
|
|
92
141
|
|
|
93
142
|
Stale penceresinde tazelemenin hatası isteği etkilemez: eski HTML pencere
|
|
94
143
|
boyunca geçerli kalır ve hata yalnızca loglanır
|
|
95
|
-
(`[html-cache]
|
|
144
|
+
(`[html-cache] background refresh failed: …`).
|
|
96
145
|
|
|
97
146
|
Aynı anahtar için eşzamanlı tazelemeler tek bir çalışmada birleştirilir
|
|
98
147
|
(`inflight` haritası): yüz eşzamanlı istek tek render'a düşer.
|
|
@@ -102,8 +151,8 @@ veri en fazla `revalidate + bir tazeleme turu` kadar geride olabilir. Bu bedel
|
|
|
102
151
|
kabul edilebilir, çünkü fiyat gibi canlı alanlar istemcide WebSocket'ten
|
|
103
152
|
güncelleniyor.
|
|
104
153
|
|
|
105
|
-
Store LRU'dur: erişilen girdi sona taşınır,
|
|
106
|
-
eski düşürülür.
|
|
154
|
+
Store LRU'dur: erişilen girdi sona taşınır, sınır (`cache().maxEntries`,
|
|
155
|
+
varsayılan 500) aşılınca en eski düşürülür.
|
|
107
156
|
|
|
108
157
|
## Ne önbelleğe yazılır
|
|
109
158
|
|
|
@@ -184,6 +233,82 @@ Ayrıntılar:
|
|
|
184
233
|
- `withRequestCache(run)` dışa açıktır; `route()` dışında (ör. kendi yazdığınız
|
|
185
234
|
bir Express handler'ında) aynı kapsamı kurmak için kullanılabilir.
|
|
186
235
|
|
|
236
|
+
## İstekler arası veri önbelleği: `withDataCache`
|
|
237
|
+
|
|
238
|
+
`cache()` yalnızca **tek bir istek** boyunca yaşar. Uzun kuyruğu API kotasından
|
|
239
|
+
korumak için gereken şey istekler arasında yaşayan, TTL'li ve kendi kendini
|
|
240
|
+
tazeleyen bir veri katmanı:
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
// lib/api/articles.js
|
|
244
|
+
import { withDataCache, reportUpstreamFailure } from "jskelet";
|
|
245
|
+
|
|
246
|
+
export async function getArticle(slug) {
|
|
247
|
+
return withDataCache(`haber:${slug}`, 600, async () => {
|
|
248
|
+
const response = await fetch(`${process.env.API_ORIGIN}/articles/${slug}`);
|
|
249
|
+
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
reportUpstreamFailure({ status: response.status, path: `/articles/${slug}` });
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return response.json();
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Aynı kalıbın sarmalayıcı biçimi — anahtar argümanlardan üretilir:
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
import { dataCache } from "jskelet";
|
|
264
|
+
|
|
265
|
+
export const getArticle = dataCache(
|
|
266
|
+
async (slug) => apiGet(`/articles/${slug}`),
|
|
267
|
+
{ key: "haber", revalidate: 600 },
|
|
268
|
+
);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Davranış:
|
|
272
|
+
|
|
273
|
+
| Durum | Sonuç |
|
|
274
|
+
| --- | --- |
|
|
275
|
+
| Taze girdi | Anında döner, `producer` çalışmaz |
|
|
276
|
+
| TTL geçmiş, bayat pencere sürüyor | Bayat değer **anında** döner, tazeleme arkada yürür |
|
|
277
|
+
| Girdi yok | `producer` beklenir |
|
|
278
|
+
| `producer` hata verdi, bayat girdi var | Bayat değer döner, uyarı: `[data-cache] producer failed, serving stale value: …` |
|
|
279
|
+
| `producer` hata verdi, girdi yok | Hata çağırana gider |
|
|
280
|
+
|
|
281
|
+
Ayrıntılar:
|
|
282
|
+
|
|
283
|
+
- **Aynı anahtarı eşzamanlı isteyen çağrılar tek upstream isteğine düşer.**
|
|
284
|
+
Isıtma turlarında kotayı en çok kurtaran davranış bu: 50 sayfa aynı endeks
|
|
285
|
+
verisini istiyorsa API bir kez çağrılır.
|
|
286
|
+
- **`null` ve `undefined` saklanmaz.** Uygulamaların HTTP istemcisi hatada
|
|
287
|
+
genellikle `null` döner; bunu saklamak geçici bir 429'u TTL boyunca "veri yok"
|
|
288
|
+
hâline dondurmak olurdu. Boş cevabı bilinçli olarak saklamak isteyen
|
|
289
|
+
`{ storeEmpty: true }` verir.
|
|
290
|
+
- **Bayat pencere HTML'dekinden uzun**: `staleFactor` varsayılanı 10, yani girdi
|
|
291
|
+
TTL'inin 11 katı boyunca acil durum yedeği olarak kalır. Bayat veri, eksik
|
|
292
|
+
sayfadan iyidir. Anahtar başına `{ staleFactor: 0 }` ile kapatılabilir.
|
|
293
|
+
- Anahtar tamamen uygulamanın: dil, sürüm, sayfa numarası gibi ayrımlar anahtara
|
|
294
|
+
yazılır (`haber:tr:v2:${slug}`).
|
|
295
|
+
- TTL `0` verildiğinde önbellek devre dışı kalır ve `producer` her çağrıda
|
|
296
|
+
çalışır — bir ayarı geçici olarak kapatmak için yeterli.
|
|
297
|
+
|
|
298
|
+
Yönetim yüzeyi:
|
|
299
|
+
|
|
300
|
+
| Fonksiyon | Ne yapar |
|
|
301
|
+
| --- | --- |
|
|
302
|
+
| `withDataCache(key, ttlSeconds, producer, options?)` | Ana giriş noktası |
|
|
303
|
+
| `dataCache(fn, { key, revalidate, … })` | Fonksiyon sarmalayıcısı |
|
|
304
|
+
| `clearDataCache(prefix?)` | Önek eşleşen girdileri (ya da tümünü) düşürür, silinen sayısını döner |
|
|
305
|
+
| `getDataCacheSize()` | Girdi sayısı |
|
|
306
|
+
| `getDataCacheEntries()` | Döküm: `{ key, stale, expiresIn }`. Değerin kendisi dönmez. |
|
|
307
|
+
|
|
308
|
+
`clearDataCache("haber:")`, "bu içerik güncellendi" webhook'unun karşılığıdır:
|
|
309
|
+
tek bir bölümün verisini düşürüp HTML'in bir sonraki tazelemesinde yeni içeriği
|
|
310
|
+
almasını sağlar.
|
|
311
|
+
|
|
187
312
|
## Degraded render: `reportUpstreamFailure`
|
|
188
313
|
|
|
189
314
|
Render sırasında upstream düştüyse çıktı eksik veri içeriyor demektir. Böyle bir
|
|
@@ -220,14 +345,40 @@ export async function apiGet(path) {
|
|
|
220
345
|
|
|
221
346
|
| Durum | Sayılır | Sonuç |
|
|
222
347
|
| --- | --- | --- |
|
|
223
|
-
| `0` (ağ hatası), `408`, `425`, `429`, `>= 500` | **Geçici** | Sayfa önbelleğe yazılmaz, uyarı: `[render] <
|
|
224
|
-
| Diğerleri (`400`, `403`, `404`, …) | **Kalıcı** | Yalnızca uyarı: `[render] <
|
|
348
|
+
| `0` (ağ hatası), `408`, `425`, `429`, `>= 500` | **Geçici** | Sayfa önbelleğe yazılmaz, uyarı: `[render] <path> was produced with missing data, not caching it (…)` |
|
|
349
|
+
| Diğerleri (`400`, `403`, `404`, …) | **Kalıcı** | Yalnızca uyarı: `[render] <path> was produced with missing data, upstream is failing permanently (…)`. Önbellek engellenmez. |
|
|
225
350
|
|
|
226
351
|
Kalıcı hataların önbelleği engellememesi bilinçli: deterministik cevaplar tekrar
|
|
227
352
|
denemekle düzelmez. Onlar yüzünden önbelleği kapatmak sayfayı her ziyarette
|
|
228
353
|
baştan render etmek olur — içerik yine aynı eksik hâliyle döner, ziyaretçi
|
|
229
354
|
sadece render süresini öder.
|
|
230
355
|
|
|
356
|
+
Eksik veriyle üretilen çıktı **paylaşılan önbelleklere de sunulmaz**: `degraded`
|
|
357
|
+
bir yanıt `public, s-maxage=…` değil `private, no-store` alır. Süreç içi
|
|
358
|
+
önbelleğe yazmama kararını CDN'de geri almak, aynı hatayı bir katman yukarıda
|
|
359
|
+
tekrarlamak olurdu. Teşhis başlığı (`X-JSkelet-Cache: MISS`) yine yazılır.
|
|
360
|
+
|
|
361
|
+
### `notFound()` geçici hataya denk gelirse
|
|
362
|
+
|
|
363
|
+
Veri gelmediği için `notFound()` çağıran bir controller, upstream rate limit'e
|
|
364
|
+
girdiğinde tüm siteyi 404'e çevirebilir — ve bu 404'ler önbelleğe girdiği için
|
|
365
|
+
geçici bir kota sorunu TTL boyunca "bu sayfa yok" cevabına dönüşür. Arama motoru
|
|
366
|
+
için bu kalıcı bir kayıp.
|
|
367
|
+
|
|
368
|
+
Framework bu durumu ayırır: render sırasında **geçici** bir upstream hatası
|
|
369
|
+
bildirilmişse `notFound()` 404 olarak servis edilmez.
|
|
370
|
+
|
|
371
|
+
| Render sırasında | `notFound()` sonucu |
|
|
372
|
+
| --- | --- |
|
|
373
|
+
| Geçici hata var (`429`, `5xx`, ağ hatası) | `503`, `Retry-After: 30`, `no-store` — önbelleğe **girmez**, sonraki istek gerçek içeriği üretir |
|
|
374
|
+
| Kalıcı hata var (`404`, `403`…) ya da hata yok | Normal `404` |
|
|
375
|
+
|
|
376
|
+
Log satırı:
|
|
377
|
+
`[render] /haber/x returned notFound() while upstream is failing (429 /api/...), serving an uncached 503 instead`
|
|
378
|
+
|
|
379
|
+
Yani upstream'in kotası dolduğunda sayfa dinamik olarak, önbelleğe yazılmadan
|
|
380
|
+
üretilir; hiçbir şey "yok" olarak dondurulmaz.
|
|
381
|
+
|
|
231
382
|
## Önbelleği yönetmek
|
|
232
383
|
|
|
233
384
|
`jskelet` şu fonksiyonları dışa açar:
|
|
@@ -303,23 +454,74 @@ Kurallar:
|
|
|
303
454
|
- Yalnızca `/` ile başlayan string'ler alınır.
|
|
304
455
|
- `prewarmSkip` öneklerinden biriyle başlayanlar atlanır. Varsayılan liste:
|
|
305
456
|
`/api/`, `/_fragment/`, `/__jskelet/`. Oturuma bağlı sayfalar ısıtılmamalı.
|
|
306
|
-
- Tekilleştirme **sırayı korur**:
|
|
307
|
-
|
|
308
|
-
koyun.
|
|
457
|
+
- Tekilleştirme **sırayı korur**: `priority` verilmediğinde uygulamanın verdiği
|
|
458
|
+
sıra anlamlıdır — en önemli sayfaları başa koyun.
|
|
309
459
|
- Bu hook tanımlı değilse ısıtma hiç kurulmaz; zamanlayıcı bile açılmaz.
|
|
310
460
|
|
|
311
461
|
### Tur mantığı
|
|
312
462
|
|
|
313
|
-
1. Liste toplanır
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
463
|
+
1. Liste toplanır. `max`'tan (varsayılan 400) uzunsa bir dilim seçilir:
|
|
464
|
+
`priority` eşleşenler **her turda** başa alınır, kalan yerler kuyruktan
|
|
465
|
+
doldurulur.
|
|
466
|
+
2. `concurrency` işçi paralel olarak istek atar (prod'da 4, dev'de 2). Dev'de
|
|
467
|
+
daha az paralellik: tarama, o an tarayıcıda açtığın sayfanın render'ıyla CPU
|
|
468
|
+
için yarışmasın.
|
|
469
|
+
3. `rps` verilmişse tur bu hızın üstüne çıkmaz — paralellik ne olursa olsun.
|
|
470
|
+
4. Başarısız yollar için, `retryDelayMs` bekledikten sonra **tek seri tekrar
|
|
471
|
+
turu** yapılır (`concurrency: 1`). Bekleme bilinçli: rate limit pencereleri
|
|
472
|
+
saniye mertebesinde, hemen tekrar denemek aynı 429'u almak demek.
|
|
473
|
+
5. Özet loglanır:
|
|
474
|
+
`[prewarm] warmed 128/130 pages, 2 failed, 5 recovered on the retry pass (12.4s)`
|
|
475
|
+
|
|
476
|
+
### Isıtma sırası: `priority`
|
|
477
|
+
|
|
478
|
+
```js
|
|
479
|
+
// jskelet.config.mjs
|
|
480
|
+
cache: () => ({
|
|
481
|
+
prewarm: {
|
|
482
|
+
priority: [
|
|
483
|
+
"/",
|
|
484
|
+
"/piyasalar/:path*",
|
|
485
|
+
/-yorumlar$/,
|
|
486
|
+
],
|
|
487
|
+
},
|
|
488
|
+
}),
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Desen sözdizimi (`/haber/:slug`) ve doğrudan `RegExp` birlikte kullanılabilir;
|
|
492
|
+
ikincisi "sonu `-yorumlar` ile bitenler" gibi desen sözdiziminin karşılamadığı
|
|
493
|
+
kurallar için. Önce yazılan önce ısınır, hiçbirine uymayan yollar kuyruğa gider
|
|
494
|
+
ve kendi aralarındaki sırayı korur.
|
|
495
|
+
|
|
496
|
+
### Damla damla ısıtma: `rotate` + `rps` + `intervalSeconds`
|
|
497
|
+
|
|
498
|
+
10.000 yolluk bir sitede tek turda her şeyi ısıtmak ne mümkün (HTML önbelleği
|
|
499
|
+
500 girdi tutar) ne de doğru (API kotası dolar). Doğru davranış listeyi zamana
|
|
500
|
+
yaymak:
|
|
501
|
+
|
|
502
|
+
```js
|
|
503
|
+
prewarm: {
|
|
504
|
+
max: 300, // her turda 300 sayfa
|
|
505
|
+
rps: 4, // saniyede en fazla 4 istek
|
|
506
|
+
intervalSeconds: 300, // 5 dakikada bir tur
|
|
507
|
+
rotate: true, // kuyruk kaldığı yerden devam eder
|
|
508
|
+
priority: ["/", "/piyasalar/:path*"],
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Bu kurulumda öncelikli sayfalar her turda tazelenir, geri kalan kuyruk turlar
|
|
513
|
+
boyunca baştan sona dolaşılır ve upstream saniyede dört isteğin üstünü hiç
|
|
514
|
+
görmez. Veri önbelleği ile birlikte kullanıldığında ikinci turdan sonra ısıtma
|
|
515
|
+
API'ye neredeyse hiç gitmez: veri katmanından okur.
|
|
516
|
+
|
|
517
|
+
Rotasyon açıkken sınırın dışında kalan yollar kaybolmuyor, bir sonraki tura
|
|
518
|
+
kalıyor; log bunu ayırt eder:
|
|
519
|
+
`… , 700 deferred to the next pass`. `rotate: false` ile klasik davranışa
|
|
520
|
+
dönülür — her tur listenin aynı ilk dilimini ısıtır ve gerisi hiç ısınmaz
|
|
521
|
+
(`… , 700 over the limit`).
|
|
522
|
+
|
|
523
|
+
Bir tur `intervalSeconds`'tan uzun sürerse yeni tur başlatılmaz; üst üste binen
|
|
524
|
+
turlar upstream'e iki kat yük bindirirdi.
|
|
323
525
|
|
|
324
526
|
İstekler `user-agent: jskelet-prewarm` (`brand.prewarmUserAgent`) ve
|
|
325
527
|
`accept-encoding: br, gzip` başlıklarıyla gider; ikincisi sıkıştırılmış gövdenin
|
|
@@ -353,10 +555,14 @@ tek seferlik deneyler config'i düzenlemeden yapılabilsin.
|
|
|
353
555
|
| Ayar | Env | `cache().prewarm` | Varsayılan |
|
|
354
556
|
| --- | --- | --- | --- |
|
|
355
557
|
| Açık/kapalı | `PREWARM=0` kapatır, `PREWARM=1` config'i ezip açar | `enabled` | `true` |
|
|
356
|
-
|
|
|
558
|
+
| Tur başına en fazla yol | `PREWARM_MAX` | `max` | `400` |
|
|
357
559
|
| Paralellik | `PREWARM_CONCURRENCY` | `concurrency` | prod 4, dev 2 |
|
|
560
|
+
| Saniyedeki istek | `PREWARM_RPS` | `rps` | `0` (sınırsız) |
|
|
358
561
|
| Başlangıç gecikmesi (ms) | `PREWARM_DELAY_MS` | `delayMs` | prod 500, dev 3000 |
|
|
562
|
+
| Tekrar turu gecikmesi (ms) | `PREWARM_RETRY_DELAY_MS` | `retryDelayMs` | `2000` |
|
|
359
563
|
| Periyot (saniye) | `PREWARM_INTERVAL_SECONDS` | `intervalSeconds` | `0` (kapalı) |
|
|
564
|
+
| Kuyruk rotasyonu | — | `rotate` | `true` |
|
|
565
|
+
| Isıtma sırası | — | `priority` | `[]` |
|
|
360
566
|
|
|
361
567
|
Sayısal ayarlar yalnızca **pozitif ve sonlu** değer kabul eder; geçersiz bir
|
|
362
568
|
değer sessizce bir sonraki katmana düşer.
|
|
@@ -392,7 +598,7 @@ turunun gerçekten `MISS` → önbellek doldurup doldurmadığını buradan gör
|
|
|
392
598
|
`cache().html` içindeki desen 0 saniye veriyor. Veya sayfa `status: 200`
|
|
393
599
|
dışında bir kod dönüyor.
|
|
394
600
|
- **Sayfa `MISS` dönüyor ama upstream sağlam.** Geçici bir upstream hatası
|
|
395
|
-
bildirilmiş olabilir; logda `
|
|
601
|
+
bildirilmiş olabilir; logda `was produced with missing data, not caching it`
|
|
396
602
|
satırını arayın.
|
|
397
603
|
- **Sürekli eski veri.** `revalidate` çok yüksek; unutmayın ki gerçek gecikme en
|
|
398
604
|
fazla `revalidate` + bir tazeleme turudur.
|
|
@@ -400,6 +606,14 @@ turunun gerçekten `MISS` → önbellek doldurup doldurmadığını buradan gör
|
|
|
400
606
|
parametreleri girdi çoğaltıyor olabilir.
|
|
401
607
|
- **Isıtma hiç çalışmıyor.** `hooks.prewarmPaths` tanımlı değil, `PREWARM=0`
|
|
402
608
|
ayarlı ya da `cache().prewarm.enabled === false`.
|
|
609
|
+
- **Isıtma turu API'yi 429'a sokuyor.** `rps` verilmemiş. `concurrency`
|
|
610
|
+
düşürmek yeterli değil; kotayı koruyan ayar toplam hız. Kalıcı çözüm veri
|
|
611
|
+
önbelleği: ikinci turdan sonra ısıtma upstream'e gitmez.
|
|
612
|
+
- **Isıtma listesi `max`'tan uzun ve sonu hiç ısınmıyor.** `rotate: false`
|
|
613
|
+
olabilir; logdaki `over the limit` ifadesi bunu gösterir.
|
|
614
|
+
- **Bir bölümün tamamı 404 dönüyor.** Upstream düşmüş olabilir. Artık bu durumda
|
|
615
|
+
404 değil önbelleğe girmeyen 503 dönüyor; logda `returned notFound() while
|
|
616
|
+
upstream is failing` satırını arayın.
|
|
403
617
|
|
|
404
618
|
## Sırada ne var
|
|
405
619
|
|
package/docs/07-yapilandirma.md
CHANGED
|
@@ -25,7 +25,7 @@ düz değer** olabilir; fonksiyon olmaları hâlinde `async` olabilirler ve `thi
|
|
|
25
25
|
config nesnesine bağlıdır. Bir bölüm hata verirse yalnızca o bölüm yok sayılır.
|
|
26
26
|
|
|
27
27
|
Config başarıyla yüklendiğinde bir özet basılır:
|
|
28
|
-
`[config] jskelet.config.mjs
|
|
28
|
+
`[config] jskelet.config.mjs loaded — 3 headers, 2 redirects, 1 cache rule`
|
|
29
29
|
|
|
30
30
|
## Tam örnek
|
|
31
31
|
|
|
@@ -62,6 +62,20 @@ export default {
|
|
|
62
62
|
devGateBypass: ["/api/healthcheck", "/robots.txt"],
|
|
63
63
|
preconnect: ["https://cdn.ornek.com"],
|
|
64
64
|
|
|
65
|
+
security: {
|
|
66
|
+
trustProxy: true,
|
|
67
|
+
cookieSecret: process.env.JSKELET_SECRET,
|
|
68
|
+
csrf: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
token: false,
|
|
71
|
+
allowedOrigins: [],
|
|
72
|
+
exclude: ["/webhook/:path*"],
|
|
73
|
+
cookieName: "csrf_token",
|
|
74
|
+
fieldName: "_csrf",
|
|
75
|
+
headerName: "x-csrf-token",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
|
|
65
79
|
navigation: {
|
|
66
80
|
prefetch: "moderate",
|
|
67
81
|
prerender: "conservative",
|
|
@@ -101,7 +115,17 @@ export default {
|
|
|
101
115
|
async cache() {
|
|
102
116
|
return {
|
|
103
117
|
html: { "/": 60, "/haber/:slug": 300 },
|
|
104
|
-
|
|
118
|
+
maxEntries: 500,
|
|
119
|
+
data: { maxEntries: 10000, staleFactor: 10 },
|
|
120
|
+
prewarm: {
|
|
121
|
+
enabled: true,
|
|
122
|
+
max: 400,
|
|
123
|
+
concurrency: 4,
|
|
124
|
+
rps: 0,
|
|
125
|
+
intervalSeconds: 0,
|
|
126
|
+
rotate: true,
|
|
127
|
+
priority: ["/", "/haber/:slug"],
|
|
128
|
+
},
|
|
105
129
|
};
|
|
106
130
|
},
|
|
107
131
|
|
|
@@ -239,6 +263,37 @@ bir yapılandırmadır.
|
|
|
239
263
|
preconnect: ["https://cdn.ornek.com", "https://api.ornek.com"]
|
|
240
264
|
```
|
|
241
265
|
|
|
266
|
+
## `security`
|
|
267
|
+
|
|
268
|
+
**Tip:** `object` — **Varsayılan:**
|
|
269
|
+
`{ trustProxy: true, cookieSecret: null, csrf: { enabled: true, token: false, … } }`
|
|
270
|
+
|
|
271
|
+
Kişiye özel sayfaların tamamı ve gerekçeleri
|
|
272
|
+
[12-panel-ve-oturum.md](./12-panel-ve-oturum.md)'de; burada alanların referansı
|
|
273
|
+
var.
|
|
274
|
+
|
|
275
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
276
|
+
| --- | --- | --- | --- |
|
|
277
|
+
| `trustProxy` | `boolean` | `true` | Express'in `trust proxy` ayarı. Ters proxy arkasında doğru protokol ve istemci IP'si için gerekli. |
|
|
278
|
+
| `cookieSecret` | `string \| null` | `null` | İmzalı cookie sırrı. Verilmezse `JSKELET_SECRET` okunur. |
|
|
279
|
+
| `csrf.enabled` | `boolean` | `true` | Origin/`Sec-Fetch-Site` kontrolü. |
|
|
280
|
+
| `csrf.token` | `boolean` | `false` | Çift gönderim token'ı katmanı. |
|
|
281
|
+
| `csrf.allowedOrigins` | `string[]` | `[]` | Kendi host'umuzun yanında kabul edilen origin'ler. |
|
|
282
|
+
| `csrf.exclude` | `string[]` | `[]` | Kontrolden muaf yollar; `source` desen sözdizimi. |
|
|
283
|
+
| `csrf.cookieName` | `string` | `"csrf_token"` | Token cookie'sinin adı. |
|
|
284
|
+
| `csrf.fieldName` | `string` | `"_csrf"` | `csrfField()`in bastığı alan adı. |
|
|
285
|
+
| `csrf.headerName` | `string` | `"x-csrf-token"` | Token'ın kabul edildiği başlık. |
|
|
286
|
+
|
|
287
|
+
`trustProxy` doğrudan internete açık bir sunucuda **kapatılmalı**: açıkken
|
|
288
|
+
istemci kendi `X-Forwarded-For` başlığını uydurabilir ve rate limit ile audit
|
|
289
|
+
log yanlış adresi görür.
|
|
290
|
+
|
|
291
|
+
CSRF kontrolü yalnızca çapraz site olduğu **belli** olan istekleri reddeder —
|
|
292
|
+
`Origin` uyuşmuyorsa ya da `Sec-Fetch-Site: cross-site` geldiyse. İkisi de yoksa
|
|
293
|
+
istek geçer, çünkü tarayıcılar çapraz origin bir POST'ta `Origin`'i her zaman
|
|
294
|
+
gönderirken webhook'lar hiç göndermez. Yine de tarayıcıdan gelmeyen uçları
|
|
295
|
+
`csrf.exclude` listesine yazmak niyeti okunur kılıyor.
|
|
296
|
+
|
|
242
297
|
## `navigation`
|
|
243
298
|
|
|
244
299
|
**Tip:** `object` — **Varsayılan:**
|
|
@@ -504,8 +559,10 @@ Ayrıntı: [03-routing.md](./03-routing.md).
|
|
|
504
559
|
|
|
505
560
|
## `cache()`
|
|
506
561
|
|
|
507
|
-
**Tip:**
|
|
508
|
-
|
|
562
|
+
**Tip:**
|
|
563
|
+
`() => { html?: Record<string, number>, maxEntries?: number, data?: object, prewarm?: object }` —
|
|
564
|
+
**Varsayılan:**
|
|
565
|
+
`{ html: {}, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, prewarm: { enabled: true, max: 400, intervalSeconds: 0 } }`
|
|
509
566
|
|
|
510
567
|
### `cache().html`
|
|
511
568
|
|
|
@@ -521,18 +578,59 @@ html: {
|
|
|
521
578
|
}
|
|
522
579
|
```
|
|
523
580
|
|
|
581
|
+
Tek istisna `route(fn, { private: true })`: bu route'ta desen eşleşse bile yok
|
|
582
|
+
sayılır. Kilidin tek yönlü olması bilinçli — ters yönde bir hata, bir
|
|
583
|
+
kullanıcının HTML'inin bir başkasına servis edilmesi anlamına geliyor.
|
|
584
|
+
|
|
585
|
+
### `cache().maxEntries`
|
|
586
|
+
|
|
587
|
+
**Tip:** `number` — **Varsayılan:** `500`
|
|
588
|
+
|
|
589
|
+
HTML önbelleğinin girdi sınırı. Girdi başına yüz kilobayt düştüğü için bu sayıyı
|
|
590
|
+
yükseltmek belleği hızla tüketir; on binlerce yollu bir siteyi buradan çözmeye
|
|
591
|
+
çalışmak yanlış katman, doğru yer `cache().data`.
|
|
592
|
+
|
|
593
|
+
### `cache().data`
|
|
594
|
+
|
|
595
|
+
Upstream veri önbelleği (`withDataCache`). Ayrıntı: [06-cache.md](./06-cache.md).
|
|
596
|
+
|
|
597
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
598
|
+
| --- | --- | --- | --- |
|
|
599
|
+
| `maxEntries` | `number` | `10000` | LRU girdi sınırı. JSON, HTML'e göre onlarca kat küçük olduğu için sınır yüksek. |
|
|
600
|
+
| `staleFactor` | `number` | `10` | TTL dolduktan sonra girdinin kaç TTL boyunca daha kullanılabileceği. `0` → bayat servis yok. |
|
|
601
|
+
|
|
524
602
|
### `cache().prewarm`
|
|
525
603
|
|
|
526
604
|
| Alan | Tip | Varsayılan | Anlamı |
|
|
527
605
|
| --- | --- | --- | --- |
|
|
528
606
|
| `enabled` | `boolean` | `true` | `false` ise ısıtma yapılmaz (`PREWARM=1` ile ezilebilir) |
|
|
529
|
-
| `max` | `number` | `400` |
|
|
607
|
+
| `max` | `number` | `400` | Bir turda en fazla kaç yol ısıtılır |
|
|
530
608
|
| `concurrency` | `number` | prod 4, dev 2 | Paralel işçi sayısı |
|
|
609
|
+
| `rps` | `number` | `0` | Saniyedeki en fazla ısıtma isteği; `0` sınırsız. Upstream kotasını koruyan ayar bu. |
|
|
531
610
|
| `delayMs` | `number` | prod 500, dev 3000 | Açılıştan sonra ilk turun gecikmesi |
|
|
611
|
+
| `retryDelayMs` | `number` | `2000` | Tekrar turundan önce beklenen süre |
|
|
532
612
|
| `intervalSeconds` | `number` | `0` | 0'dan büyükse tur periyodik tekrarlanır |
|
|
613
|
+
| `rotate` | `boolean` | `true` | Liste `max`'tan uzunsa periyodik turlar kaldığı yerden devam eder |
|
|
614
|
+
| `priority` | `(string \| RegExp)[]` | `[]` | Isıtma sırası; eşleşen yollar her turda başa alınır |
|
|
533
615
|
|
|
534
|
-
|
|
535
|
-
|
|
616
|
+
`priority` iki biçim kabul eder: config'in her yerinde geçerli olan desen
|
|
617
|
+
sözdizimi ve doğrudan `RegExp`. Önce yazılan önce ısınır.
|
|
618
|
+
|
|
619
|
+
```js
|
|
620
|
+
prewarm: {
|
|
621
|
+
max: 500,
|
|
622
|
+
rps: 4,
|
|
623
|
+
intervalSeconds: 300,
|
|
624
|
+
priority: [
|
|
625
|
+
"/", // ana sayfa
|
|
626
|
+
"/piyasalar/:path*", // tüm piyasa bölümü
|
|
627
|
+
/-yorumlar$/, // desen sözdiziminin karşılamadığı kural
|
|
628
|
+
],
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
Sayısal alanların her biri aynı adı taşıyan ortam değişkeniyle ezilebilir; env
|
|
633
|
+
önceliklidir. Ayrıntı: [06-cache.md](./06-cache.md).
|
|
536
634
|
|
|
537
635
|
## `hooks`
|
|
538
636
|
|
|
@@ -622,11 +720,14 @@ basılmaz.
|
|
|
622
720
|
| `NODE_ENV` | her yer | `production` (start/build), `development` (dev) | Dev overlay, EJS cache, manifest yeniden okuma, route hata davranışı ve prewarm varsayılanlarını belirler. `jskelet dev` bunu kendisi ayarlar — `cross-env` gerekmez. |
|
|
623
721
|
| `PORT` | `startServer` | `3000` | Dinlenecek port |
|
|
624
722
|
| `HOST` | `startServer` | `0.0.0.0` | Bağlanılacak arayüz |
|
|
723
|
+
| `JSKELET_SECRET` | `jskelet/cookies` | — | İmzalı cookie sırrı. `security.cookieSecret` verilmediğinde buradan okunur; ikisi de yoksa imzalı cookie API'si hata verir. [12](./12-panel-ve-oturum.md) |
|
|
625
724
|
| `DEV_TOKEN` | `devGate`, `prewarm` | — | Ayarlıysa token taşımayan her isteğe 404 döner. Isıtma token'ı çerez olarak taşır. [09](./09-dev-araclari.md) |
|
|
626
725
|
| `PREWARM` | `startPrewarm` | — | `0` ısıtmayı kapatır; `1` config'teki `enabled: false`'u ezip açar |
|
|
627
726
|
| `PREWARM_MAX` | `prewarm` | `400` | En fazla kaç yol ısıtılır |
|
|
628
727
|
| `PREWARM_CONCURRENCY` | `prewarm` | prod 4, dev 2 | Paralel işçi sayısı |
|
|
728
|
+
| `PREWARM_RPS` | `prewarm` | `0` | Saniyedeki en fazla ısıtma isteği; `0` sınırsız |
|
|
629
729
|
| `PREWARM_DELAY_MS` | `startPrewarm` | prod 500, dev 3000 | İlk turun gecikmesi |
|
|
730
|
+
| `PREWARM_RETRY_DELAY_MS` | `prewarm` | `2000` | Tekrar turundan önceki bekleme |
|
|
630
731
|
| `PREWARM_INTERVAL_SECONDS` | `startPrewarm` | `0` | 0'dan büyükse periyodik tur |
|
|
631
732
|
| `JSKELET_VERBOSE` | `jskelet dev` | — | `1` ise restart'ta değişen dosyaların tamamı listelenir |
|
|
632
733
|
| `JSKELET_COLOR` | `jskelet/log` | — | `1` ise renk zorlanır. Alt süreçler boruya yazdığı için renk algılaması kapanır; `jskelet dev` bunu kendisi ayarlar. |
|
package/docs/08-build.md
CHANGED
|
@@ -69,7 +69,7 @@ dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
|
|
|
69
69
|
Build çalışmadıysa uygulama yine ayağa kalkar: `hasAsset()` false olur, layout
|
|
70
70
|
stylesheet ve script etiketlerini hiç basmaz. `jskelet build` unutulduğunda hata
|
|
71
71
|
yerine stilsiz ama çalışan bir sayfa görürsünüz. Manifest hiç yoksa bir kez uyarı
|
|
72
|
-
basılır:
|
|
72
|
+
basılır: ``[assets] no manifest — run `jskelet build`.``
|
|
73
73
|
|
|
74
74
|
Manifest **dev'de her istekte** yeniden okunur (watch build hash'leri
|
|
75
75
|
değiştirir), prod'da bir kez.
|
|
@@ -244,8 +244,8 @@ Son satır için iki güvenlik ağı var: ad taşıyan yapılandırma alanları
|
|
|
244
244
|
sembolleri okuyup eksik olan için tek seferlik uyarı basar:
|
|
245
245
|
|
|
246
246
|
```
|
|
247
|
-
[icon] sprite
|
|
248
|
-
build/tasks/icons.mjs
|
|
247
|
+
[icon] missing from sprite: x-logo-regular — write the name as a literal or add
|
|
248
|
+
it to the build/tasks/icons.mjs scan.
|
|
249
249
|
```
|
|
250
250
|
|
|
251
251
|
Bu uyarıyı görürseniz ya adı sabit yazın, ya `icons.scan` listesine ilgili
|
|
@@ -352,7 +352,7 @@ karşılaşmaması.
|
|
|
352
352
|
kontrol edin.
|
|
353
353
|
- **Bazı Tailwind sınıfları çalışmıyor.** `@source` direktifi eksik olan bir
|
|
354
354
|
dizinde yazılmışlar.
|
|
355
|
-
- **İkon boş görünüyor.** Sprite'ta o sembol yok; dev'de `[icon] sprite
|
|
355
|
+
- **İkon boş görünüyor.** Sprite'ta o sembol yok; dev'de `[icon] missing from sprite`
|
|
356
356
|
uyarısına bakın.
|
|
357
357
|
- **Island'lar hiç açılmıyor.** `main.js` manifest'te yok (entry dizini boş ya
|
|
358
358
|
da build atlanmış) veya bir build hatası var.
|
package/docs/09-dev-araclari.md
CHANGED
|
@@ -161,6 +161,11 @@ Gösterdikleri:
|
|
|
161
161
|
- **Prewarm:** ısıtma turunun ilerlemesi; panelden elle tetiklenebilir, tek tek
|
|
162
162
|
yollar tekrar denenebilir.
|
|
163
163
|
- **Süreç:** pid, Node sürümü, uptime, RSS ve heap kullanımı.
|
|
164
|
+
- **Sürüm:** kurulu JSkelet sürümü ve npm'deki `latest` ile karşılaştırması.
|
|
165
|
+
Yeni bir sürüm varsa **Server** sekmesinde `update` rozeti ve yükseltme
|
|
166
|
+
komutunu kopyalayan bir satır çıkar. Yoklama açılıştan 1,5 saniye sonra bir
|
|
167
|
+
kez yapılır, sonucu 6 saat boyunca `os.tmpdir()` içinde saklanır ve ağ yoksa
|
|
168
|
+
sessizce atlanır. `JSKELET_VERSION_CHECK=0` ile tamamen kapatılır.
|
|
164
169
|
|
|
165
170
|
Isıtma istekleri (`user-agent: jskelet-prewarm`) hem terminalden hem istek
|
|
166
171
|
listesinden filtrelenir: yüzlerce istek görünümü doldurmasın. İlerleme baloncuğun
|