jskelet 0.1.4 → 0.1.6
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/CHANGELOG.md +29 -0
- package/docs/06-cache.md +73 -8
- package/docs/07-yapilandirma.md +15 -5
- package/docs/09-dev-araclari.md +31 -6
- package/docs/11-tasima.md +2 -3
- package/docs/en/06-caching.md +73 -8
- package/docs/en/07-configuration.md +16 -5
- package/docs/en/09-dev-tools.md +30 -4
- package/docs/en/11-migration.md +2 -3
- package/package.json +1 -1
- package/src/client/devtools/overlay.js +111 -46
- package/src/config/index.js +8 -0
- package/src/index.js +1 -0
- package/src/server/cache-deps.js +42 -0
- package/src/server/create-app.js +11 -0
- package/src/server/data-cache.js +23 -10
- package/src/server/dev/devtools.js +101 -15
- package/src/server/dev/socket.js +157 -0
- package/src/server/html-cache.js +312 -9
- package/src/server/prewarm.js +23 -10
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,24 @@ one is listed under a **Breaking** heading.
|
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
|
+
- Targeted HTML invalidation: `invalidateHtmlCache(target, { hard })` takes a
|
|
14
|
+
path, the config pattern syntax (`/news/:slug`), a regular expression or a list
|
|
15
|
+
of them, and returns how many entries were affected. By default it **stales**
|
|
16
|
+
the entries rather than deleting them, so a webhook that touches hundreds of
|
|
17
|
+
pages does not turn into hundreds of cold renders at the worst possible moment:
|
|
18
|
+
visitors keep getting the old HTML while the refresh runs in the background,
|
|
19
|
+
once per key. Matching is done against the path, so every query variant of a
|
|
20
|
+
page is covered by one call, and a render already in flight when the purge
|
|
21
|
+
arrives is not stored.
|
|
22
|
+
- `clearDataCache()` now refreshes the HTML too. The `withDataCache` keys read
|
|
23
|
+
during a render are recorded, so dropping `news:abc` stales every page that
|
|
24
|
+
actually read it — the article, the home page listing it and the tag page —
|
|
25
|
+
without the application declaring any tags. Turn it off with
|
|
26
|
+
`cache().trackDependencies: false`; `getHtmlCacheEntries()` reports the
|
|
27
|
+
dependency count per page as `deps`.
|
|
28
|
+
- Invalidated paths go to the front of the next prewarm pass, so an updated page
|
|
29
|
+
is refreshed without waiting for a visitor, while still respecting the `rps`
|
|
30
|
+
limit. The pass summary counts them separately.
|
|
13
31
|
- An upstream data cache: `withDataCache(key, ttlSeconds, producer)` and the
|
|
14
32
|
`dataCache(fn, { key, revalidate })` wrapper, with `clearDataCache(prefix?)`,
|
|
15
33
|
`getDataCacheSize()` and `getDataCacheEntries()` alongside them. It keeps JSON
|
|
@@ -47,6 +65,17 @@ one is listed under a **Breaking** heading.
|
|
|
47
65
|
|
|
48
66
|
### Changed
|
|
49
67
|
|
|
68
|
+
- The dev tools panel is now fed over a WebSocket (`<devBasePath>/ws`) instead of
|
|
69
|
+
polling `/stats` every two seconds. The server pushes statistics as they change
|
|
70
|
+
and sends live reload and CSS hot-swap events over the same connection, so an
|
|
71
|
+
open tab no longer keeps hitting the server while the panel is closed. No new
|
|
72
|
+
dependency is involved; if the socket cannot be opened, the panel falls back to
|
|
73
|
+
the previous SSE plus polling path.
|
|
74
|
+
- Prewarming no longer holds up the rest of the dev server. In development it now
|
|
75
|
+
runs with a single worker and a default limit of 4 requests per second
|
|
76
|
+
(`prewarm.rps` / `PREWARM_RPS` still override it), so page requests and the dev
|
|
77
|
+
panel stay responsive while a warm-up round is going on. Production behaviour
|
|
78
|
+
is unchanged.
|
|
50
79
|
- `notFound()` is no longer served as a 404 when a transient upstream failure
|
|
51
80
|
(`429`, `5xx`, network error) happened during the same render. The page is
|
|
52
81
|
retried first and, if upstream is still failing, responds with an uncached
|
package/docs/06-cache.md
CHANGED
|
@@ -306,8 +306,9 @@ Yönetim yüzeyi:
|
|
|
306
306
|
| `getDataCacheEntries()` | Döküm: `{ key, stale, expiresIn }`. Değerin kendisi dönmez. |
|
|
307
307
|
|
|
308
308
|
`clearDataCache("haber:")`, "bu içerik güncellendi" webhook'unun karşılığıdır:
|
|
309
|
-
tek bir bölümün verisini
|
|
310
|
-
|
|
309
|
+
tek bir bölümün verisini düşürür ve **o veriyi okumuş HTML sayfalarını da**
|
|
310
|
+
bayatlatır, yani güncelleme TTL beklenmeden görünür. Ayrıntısı aşağıda,
|
|
311
|
+
"Otomatik bağımlılık" bölümünde.
|
|
311
312
|
|
|
312
313
|
## Degraded render: `reportUpstreamFailure`
|
|
313
314
|
|
|
@@ -437,9 +438,70 @@ düşer.
|
|
|
437
438
|
| Fonksiyon | Ne yapar |
|
|
438
439
|
| --- | --- |
|
|
439
440
|
| `withHtmlCache(key, ttlSeconds, producer)` | Önbelleği doğrudan kullanmak için. `ttlSeconds` 0 ise producer her zaman çalışır. |
|
|
441
|
+
| `invalidateHtmlCache(target, options?)` | Eşleşen sayfaları bayatlatır (ya da `{ hard: true }` ile düşürür), etkilenen sayı döner. |
|
|
440
442
|
| `clearHtmlCache()` | Store'u tamamen boşaltır. |
|
|
441
443
|
| `getHtmlCacheSize()` | Girdi sayısı. |
|
|
442
|
-
| `getHtmlCacheEntries()` | Döküm: `{ key, bytes, status, stale, expiresIn, encodings }`. HTML gövdesi dönmez, yalnızca boyutu. |
|
|
444
|
+
| `getHtmlCacheEntries()` | Döküm: `{ key, bytes, status, stale, expiresIn, encodings, deps }`. HTML gövdesi dönmez, yalnızca boyutu. |
|
|
445
|
+
|
|
446
|
+
### Hedefli invalidation
|
|
447
|
+
|
|
448
|
+
TTL'i beklemekle tüm önbelleği boşaltmak arasındaki boşluğu `invalidateHtmlCache()`
|
|
449
|
+
doldurur:
|
|
450
|
+
|
|
451
|
+
```js
|
|
452
|
+
import { invalidateHtmlCache } from "jskelet";
|
|
453
|
+
|
|
454
|
+
invalidateHtmlCache("/haber/abc"); // o yol ve altı
|
|
455
|
+
invalidateHtmlCache("/haber/:slug"); // desen sözdizimi
|
|
456
|
+
invalidateHtmlCache([/-yorumlar$/, "/"]); // RegExp ve liste
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Varsayılan davranış **bayatlatmaktır**, silmek değil: girdi süresi geçmiş
|
|
460
|
+
sayılır ve normal stale-while-revalidate yoluna düşer. Bir webhook beş yüz
|
|
461
|
+
sayfayı birden düşürdüğünde sert silme, tam da içeriğin güncellendiği anda beş
|
|
462
|
+
yüz soğuk render başlatır ve upstream'i döver. Bayatlatmada ziyaretçi eski
|
|
463
|
+
HTML'i beklemeden alır, tazeleme arkada ve anahtar başına tek seferde koşar.
|
|
464
|
+
Eski HTML'in gerçekten geçersiz olduğu durumlar için `{ hard: true }`.
|
|
465
|
+
|
|
466
|
+
Anahtar `yol?query` olduğundan eşleştirme **yol kısmına** yapılır: bir yolun
|
|
467
|
+
bütün query varyantları (`?utm_source=…` dahil) tek çağrıyla düşer. Düz
|
|
468
|
+
string'te önek segment sınırında kesilir — `/haber` kuralı `/haberler`i
|
|
469
|
+
etkilemez.
|
|
470
|
+
|
|
471
|
+
Uçuşta olan bir render de hedeflenir: purge'den önce başlamış bir tur, sonucu
|
|
472
|
+
artık eski veriyi taşıdığı için önbelleğe **yazılmaz** ve bir sonraki istek yeni
|
|
473
|
+
bir tur başlatır.
|
|
474
|
+
|
|
475
|
+
### Otomatik bağımlılık: `clearDataCache` HTML'i de tazeler
|
|
476
|
+
|
|
477
|
+
Hangi sayfanın hangi içerikten etkilendiğini bildirmek gerekmez. Render
|
|
478
|
+
sırasında okunan her `withDataCache` anahtarı kaydedilir; `clearDataCache()` bir
|
|
479
|
+
anahtarı düşürdüğünde onu **fiilen okumuş** bütün HTML girdileri bayatlar.
|
|
480
|
+
|
|
481
|
+
```js
|
|
482
|
+
// "bu haber güncellendi" webhook'u
|
|
483
|
+
clearDataCache(`haber:${slug}`);
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Bu tek satır haber detayını, o haberi listeleyen ana sayfayı ve etiket
|
|
487
|
+
sayfasını birlikte tazeler — çünkü üçü de o anahtarı okumuştu. Elle tag'lemede
|
|
488
|
+
en sık yapılan hata (detayı işaretleyip listeyi unutmak) burada yapısal olarak
|
|
489
|
+
mümkün değil: bildirim değil, gözlem var.
|
|
490
|
+
|
|
491
|
+
Ayrıntılar:
|
|
492
|
+
|
|
493
|
+
- Bağımlılık **her tazelemede yeniden** toplanır; sayfanın okuduğu anahtarlar
|
|
494
|
+
zamanla değişebilir.
|
|
495
|
+
- Render sürerken gelen bir purge de yakalanır: o turun çıktısı "doğduğu anda
|
|
496
|
+
bayat" olacağı için önbelleğe yazılmaz.
|
|
497
|
+
- Sayfa başına bağımlılık sayısı `getHtmlCacheEntries()` dökümünde `deps`
|
|
498
|
+
alanında görünür. Bir invalidation beklediğiniz sayfayı tazelemiyorsa önce
|
|
499
|
+
buraya bakın: sayfa o veriyi `withDataCache` üzerinden okumuyor olabilir.
|
|
500
|
+
- `withDataCache` kullanmayan bir uygulamada kaydedilecek bir şey yoktur;
|
|
501
|
+
`cache().trackDependencies: false` ile izleme tamamen kapatılabilir.
|
|
502
|
+
- Bayatlatılan yollar ısıtma kuyruğunun **başına** alınır. `prewarm` kuruluysa
|
|
503
|
+
sayfa, ziyaretçi gelmesini beklemeden tazelenir ve tur özeti bunu ayırt eder:
|
|
504
|
+
`[prewarm] warmed 12/12 pages, 3 invalidated (0.4s)`.
|
|
443
505
|
|
|
444
506
|
Bir yönetim ucu yazmak için:
|
|
445
507
|
|
|
@@ -514,10 +576,13 @@ Kurallar:
|
|
|
514
576
|
1. Liste toplanır. `max`'tan (varsayılan 400) uzunsa bir dilim seçilir:
|
|
515
577
|
`priority` eşleşenler **her turda** başa alınır, kalan yerler kuyruktan
|
|
516
578
|
doldurulur.
|
|
517
|
-
2. `concurrency` işçi paralel olarak istek atar (prod'da 4, dev'de
|
|
518
|
-
|
|
519
|
-
|
|
579
|
+
2. `concurrency` işçi paralel olarak istek atar (prod'da 4, dev'de 1). Dev'de
|
|
580
|
+
tek işçi: tarama, o an tarayıcıda açtığın sayfanın render'ıyla CPU için
|
|
581
|
+
yarışmasın.
|
|
520
582
|
3. `rps` verilmişse tur bu hızın üstüne çıkmaz — paralellik ne olursa olsun.
|
|
583
|
+
Dev'de varsayılan olarak saniyede 4 istek uygulanır: render tek bir olay
|
|
584
|
+
döngüsünde çalıştığı için aralıksız bir tur, sayfa isteklerini ve dev
|
|
585
|
+
panelinin canlı kanalını arkasında bekletiyor.
|
|
521
586
|
4. Başarısız yollar için, `retryDelayMs` bekledikten sonra **tek seri tekrar
|
|
522
587
|
turu** yapılır (`concurrency: 1`). Bekleme bilinçli: rate limit pencereleri
|
|
523
588
|
saniye mertebesinde, hemen tekrar denemek aynı 429'u almak demek.
|
|
@@ -607,8 +672,8 @@ tek seferlik deneyler config'i düzenlemeden yapılabilsin.
|
|
|
607
672
|
| --- | --- | --- | --- |
|
|
608
673
|
| Açık/kapalı | `PREWARM=0` kapatır, `PREWARM=1` config'i ezip açar | `enabled` | `true` |
|
|
609
674
|
| Tur başına en fazla yol | `PREWARM_MAX` | `max` | `400` |
|
|
610
|
-
| Paralellik | `PREWARM_CONCURRENCY` | `concurrency` | prod 4, dev
|
|
611
|
-
| Saniyedeki istek | `PREWARM_RPS` | `rps` | `0` (sınırsız) |
|
|
675
|
+
| Paralellik | `PREWARM_CONCURRENCY` | `concurrency` | prod 4, dev 1 |
|
|
676
|
+
| Saniyedeki istek | `PREWARM_RPS` | `rps` | prod `0` (sınırsız), dev 4 |
|
|
612
677
|
| Başlangıç gecikmesi (ms) | `PREWARM_DELAY_MS` | `delayMs` | prod 500, dev 3000 |
|
|
613
678
|
| Tekrar turu gecikmesi (ms) | `PREWARM_RETRY_DELAY_MS` | `retryDelayMs` | `2000` |
|
|
614
679
|
| Periyot (saniye) | `PREWARM_INTERVAL_SECONDS` | `intervalSeconds` | `0` (kapalı) |
|
package/docs/07-yapilandirma.md
CHANGED
|
@@ -560,9 +560,9 @@ Ayrıntı: [03-routing.md](./03-routing.md).
|
|
|
560
560
|
## `cache()`
|
|
561
561
|
|
|
562
562
|
**Tip:**
|
|
563
|
-
`() => { html?: Record<string, number>, maxEntries?: number, data?: object, trackUpstream?: boolean, transientRetry?: object | false, prewarm?: object }` —
|
|
563
|
+
`() => { html?: Record<string, number>, maxEntries?: number, data?: object, trackUpstream?: boolean, trackDependencies?: boolean, transientRetry?: object | false, prewarm?: object }` —
|
|
564
564
|
**Varsayılan:**
|
|
565
|
-
`{ html: {}, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, trackUpstream: true, transientRetry: { attempts: 1, delayMs: 300 }, prewarm: { enabled: true, max: 400, intervalSeconds: 0 } }`
|
|
565
|
+
`{ html: {}, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, trackUpstream: true, trackDependencies: true, transientRetry: { attempts: 1, delayMs: 300 }, prewarm: { enabled: true, max: 400, intervalSeconds: 0 } }`
|
|
566
566
|
|
|
567
567
|
### `cache().html`
|
|
568
568
|
|
|
@@ -607,6 +607,16 @@ Açıkken `globalThis.fetch` sarılır ve render sırasındaki geçici upstream
|
|
|
607
607
|
hataları (`429`, `5xx`, ağ) kendiliğinden bildirilir; `reportUpstreamFailure()`
|
|
608
608
|
çağırmak gerekmez. `fetch`i kendisi saran bir uygulama bunu kapatabilir.
|
|
609
609
|
|
|
610
|
+
### `cache().trackDependencies`
|
|
611
|
+
|
|
612
|
+
**Tip:** `boolean` — **Varsayılan:** `true`
|
|
613
|
+
|
|
614
|
+
Açıkken bir render'ın okuduğu `withDataCache` anahtarları kaydedilir ve
|
|
615
|
+
`clearDataCache()` o veriyi okumuş HTML sayfalarını da bayatlatır — hedefli
|
|
616
|
+
invalidation için uygulamanın hiçbir şey bildirmesi gerekmez
|
|
617
|
+
([06-cache.md](./06-cache.md)). `withDataCache` kullanmayan bir uygulamada
|
|
618
|
+
kaydedilecek bir şey yok; kapatmak bağlam kurma maliyetini de kaldırır.
|
|
619
|
+
|
|
610
620
|
### `cache().transientRetry`
|
|
611
621
|
|
|
612
622
|
**Tip:** `{ attempts?: number, delayMs?: number } | false` —
|
|
@@ -623,8 +633,8 @@ Ayrıntı: [06-cache.md](./06-cache.md).
|
|
|
623
633
|
| --- | --- | --- | --- |
|
|
624
634
|
| `enabled` | `boolean` | `true` | `false` ise ısıtma yapılmaz (`PREWARM=1` ile ezilebilir) |
|
|
625
635
|
| `max` | `number` | `400` | Bir turda en fazla kaç yol ısıtılır |
|
|
626
|
-
| `concurrency` | `number` | prod 4, dev
|
|
627
|
-
| `rps` | `number` | `0
|
|
636
|
+
| `concurrency` | `number` | prod 4, dev 1 | Paralel işçi sayısı |
|
|
637
|
+
| `rps` | `number` | prod `0`, dev 4 | Saniyedeki en fazla ısıtma isteği; `0` sınırsız. Upstream kotasını koruyan ayar bu. Dev'deki varsayılan fren, ısıtmanın sayfa isteklerini bekletmemesi için. |
|
|
628
638
|
| `delayMs` | `number` | prod 500, dev 3000 | Açılıştan sonra ilk turun gecikmesi |
|
|
629
639
|
| `retryDelayMs` | `number` | `2000` | Tekrar turundan önce beklenen süre |
|
|
630
640
|
| `intervalSeconds` | `number` | `0` | 0'dan büyükse tur periyodik tekrarlanır |
|
|
@@ -742,7 +752,7 @@ basılmaz.
|
|
|
742
752
|
| `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) |
|
|
743
753
|
| `PREWARM` | `startPrewarm` | — | `0` ısıtmayı kapatır; `1` config'teki `enabled: false`'u ezip açar |
|
|
744
754
|
| `PREWARM_MAX` | `prewarm` | `400` | En fazla kaç yol ısıtılır |
|
|
745
|
-
| `PREWARM_CONCURRENCY` | `prewarm` | prod 4, dev
|
|
755
|
+
| `PREWARM_CONCURRENCY` | `prewarm` | prod 4, dev 1 | Paralel işçi sayısı |
|
|
746
756
|
| `PREWARM_RPS` | `prewarm` | `0` | Saniyedeki en fazla ısıtma isteği; `0` sınırsız |
|
|
747
757
|
| `PREWARM_DELAY_MS` | `startPrewarm` | prod 500, dev 3000 | İlk turun gecikmesi |
|
|
748
758
|
| `PREWARM_RETRY_DELAY_MS` | `prewarm` | `2000` | Tekrar turundan önceki bekleme |
|
package/docs/09-dev-araclari.md
CHANGED
|
@@ -111,9 +111,9 @@ Restart satırı değişen dosyayı ya da sayısını gösterir:
|
|
|
111
111
|
|
|
112
112
|
## CSS hot-swap ve tam yenileme
|
|
113
113
|
|
|
114
|
-
Dev sunucusu `.jskelet/manifest.json` dosyasını izler ve
|
|
115
|
-
(`<devBasePath>/
|
|
116
|
-
|
|
114
|
+
Dev sunucusu `.jskelet/manifest.json` dosyasını izler ve olayları canlı kanal
|
|
115
|
+
(`<devBasePath>/ws`) üzerinden tarayıcıya yayınlar. Manifest her build turunda
|
|
116
|
+
yeniden yazıldığı için değişiklik tespiti manifest üzerinden yapılır.
|
|
117
117
|
|
|
118
118
|
| Değişen | Davranış |
|
|
119
119
|
| --- | --- |
|
|
@@ -131,6 +131,29 @@ Sunucu yeniden başladığında overlay bunu **boot kimliğinden** anlar: her s
|
|
|
131
131
|
kendine özgü bir `boot` değeri yayınlar, overlay değişikliği görüp "restarted"
|
|
132
132
|
bilgisini gösterir ve kendi durumunu sıfırlamaz.
|
|
133
133
|
|
|
134
|
+
## Canlı kanal
|
|
135
|
+
|
|
136
|
+
Overlay'e giden her şey — istatistikler, live reload ve CSS hot-swap olayları —
|
|
137
|
+
tek bir WebSocket üzerinden gelir (`<devBasePath>/ws`). Panel eskiden
|
|
138
|
+
istatistikleri iki saniyede bir çekiyordu; açık her sekme, panel kapalıyken bile
|
|
139
|
+
sunucuya sürekli istek atıyordu. Artık sunucu değişiklik oldukça iter: bir istek
|
|
140
|
+
ya da hata kaydedildiğinde (120 ms birleştirilerek), ısıtma sürerken saniyede
|
|
141
|
+
ve zamana bağlı alanlar (uptime, bellek, ısıtma sayacı) tazelensin diye iki
|
|
142
|
+
saniyede bir. Kalp atışı bilinçli olarak ısıtmadan bağımsız: kanalın temposu bir
|
|
143
|
+
arka plan işine göre değişirse panel de o işin ritmine bağlanmış olur. Bağlı
|
|
144
|
+
panel yoksa hiçbir şey hesaplanmaz.
|
|
145
|
+
|
|
146
|
+
El sıkışma HTTP `upgrade` olayında geçtiği ve o olay middleware zincirine hiç
|
|
147
|
+
uğramadığı için kanal `listen` sonrası doğrudan sunucuya bağlanır
|
|
148
|
+
(`attachDevSocket`). Sunucu tarafı `ws` gibi bir bağımlılık kullanmaz: yalnızca
|
|
149
|
+
sunucu→istemci metin çerçevesi yazmak ve istemcinin ping/close çerçevelerini
|
|
150
|
+
yanıtlamak gerekiyor.
|
|
151
|
+
|
|
152
|
+
Soket hiç açılamazsa (araya giren bir proxy WebSocket'i geçirmiyor olabilir)
|
|
153
|
+
overlay eski yola düşer: `/events` SSE akışı + `/stats` yoklaması. Soket kurulup
|
|
154
|
+
sonra düşerse — yani sunucu yeniden başlıyorsa — yarım saniyede bir yeniden
|
|
155
|
+
bağlanır ve gösterge bu sırada "bağlantı yok" der.
|
|
156
|
+
|
|
134
157
|
## Devtools overlay
|
|
135
158
|
|
|
136
159
|
Sağ altta yüzen bir baloncuk; `Alt+D` ile açılır, `Esc` ya da karartma alanına
|
|
@@ -236,8 +259,9 @@ Rapor katmanı yalnızca development'ta yüklenir, üretim çıktısına hiç gi
|
|
|
236
259
|
| --- | --- | --- |
|
|
237
260
|
| `/overlay.js` | GET | Overlay script'i |
|
|
238
261
|
| `/logo.png` | GET | Overlay logosu |
|
|
239
|
-
| `/
|
|
240
|
-
| `/
|
|
262
|
+
| `/ws` | GET (upgrade) | Canlı kanal: istatistikler, live reload ve CSS hot-swap olayları |
|
|
263
|
+
| `/events` | GET | SSE: yalnızca WebSocket kurulamazsa kullanılan yedek olay akışı |
|
|
264
|
+
| `/stats` | GET | Anlık istatistikler; aynı yedek yolun veri ucu |
|
|
241
265
|
| `/report` | GET | Rapor sayfası (HTML) |
|
|
242
266
|
| `/report.js` | GET | Rapor sayfasının script'i |
|
|
243
267
|
| `/report/data` | GET | Raporun tek veri kaynağı (JSON) |
|
|
@@ -294,7 +318,8 @@ yayına açılmamış bir ortam yönlendirme kurallarını bile dışarıya sız
|
|
|
294
318
|
| Bozuk route modülü | Uyarı + atla | Fırlat |
|
|
295
319
|
| Devtools ve rapor | Mount edilir | Hiç yüklenmez |
|
|
296
320
|
| `globalThis.fetch` | Sarılır (ölçüm) | Dokunulmaz |
|
|
297
|
-
| Prewarm paralelliği |
|
|
321
|
+
| Prewarm paralelliği | 1 | 4 |
|
|
322
|
+
| Prewarm hız freni | saniyede 4 istek | Sınırsız |
|
|
298
323
|
| Prewarm gecikmesi | 3000 ms | 500 ms |
|
|
299
324
|
| Eksik ikon uyarısı | Verilir | Verilmez |
|
|
300
325
|
| Precompress | Watch'ta çalışmaz | Çalışır |
|
package/docs/11-tasima.md
CHANGED
|
@@ -52,7 +52,8 @@ alt kümesine benzetildi — `next.config` sözdizimi, Metadata API, `notFound()
|
|
|
52
52
|
| `fetch(..., { next: { revalidate } })` | — | Önbellek sayfa düzeyinde |
|
|
53
53
|
| `unstable_cache` | — | İstek üstü veri önbelleği yok; sayfa önbelleği var |
|
|
54
54
|
| React `cache()` | `cache()` | Aynı davranış: istek içi memoizasyon |
|
|
55
|
-
| `revalidatePath()` | `
|
|
55
|
+
| `revalidatePath()` | `invalidateHtmlCache("/haber/:slug")` | Yol, desen ya da RegExp; varsayılan olarak siler değil bayatlatır |
|
|
56
|
+
| `revalidateTag()` | `clearDataCache("haber:")` | Tag bildirmeye gerek yok: bağımlılık render sırasında gözlenir ([06](./06-cache.md)) |
|
|
56
57
|
| `cookies()`, `headers()` | `ctx.req.headers`, `ctx.req.cookies`* | Express nesnesine doğrudan erişim |
|
|
57
58
|
| `dynamic = "force-dynamic"` | `revalidate` vermemek | Önbellek kapalı demek |
|
|
58
59
|
|
|
@@ -94,8 +95,6 @@ Bunları taşıma planında baştan hesaba katın:
|
|
|
94
95
|
önce belgeyi hazırlar, `viewTransition` geçişi yumuşatır
|
|
95
96
|
([07](./07-yapilandirma.md)).
|
|
96
97
|
- **Server Actions.** Form gönderimleri normal `app.post(...)` handler'larıdır.
|
|
97
|
-
- **Tek tek yol geçersizleme (`revalidatePath`).** Şimdilik tüm önbelleği
|
|
98
|
-
temizlemek (`clearHtmlCache()`) ya da TTL'in dolmasını beklemek var.
|
|
99
98
|
- **Otomatik görsel optimizasyonu (istek anında).** Optimizasyon build zamanında
|
|
100
99
|
yapılır ve yalnızca `public/` altındaki yerel görselleri kapsar; uzak görseller
|
|
101
100
|
olduğu gibi basılır.
|
package/docs/en/06-caching.md
CHANGED
|
@@ -315,8 +315,9 @@ The management surface:
|
|
|
315
315
|
| `getDataCacheEntries()` | A dump: `{ key, stale, expiresIn }`. The value itself is not returned. |
|
|
316
316
|
|
|
317
317
|
`clearDataCache("news:")` is the counterpart of a "this content was updated"
|
|
318
|
-
webhook: it drops one section's data
|
|
319
|
-
|
|
318
|
+
webhook: it drops one section's data **and stales the HTML pages that read it**,
|
|
319
|
+
so the update shows up without waiting for a TTL. See "Automatic dependencies"
|
|
320
|
+
below.
|
|
320
321
|
|
|
321
322
|
## Degraded render: `reportUpstreamFailure`
|
|
322
323
|
|
|
@@ -447,9 +448,71 @@ through to the 503.
|
|
|
447
448
|
| Function | What it does |
|
|
448
449
|
| --- | --- |
|
|
449
450
|
| `withHtmlCache(key, ttlSeconds, producer)` | For using the cache directly. If `ttlSeconds` is 0 the producer always runs. |
|
|
451
|
+
| `invalidateHtmlCache(target, options?)` | Stales the matching pages (or drops them with `{ hard: true }`) and returns how many were affected. |
|
|
450
452
|
| `clearHtmlCache()` | Empties the store completely. |
|
|
451
453
|
| `getHtmlCacheSize()` | The number of entries. |
|
|
452
|
-
| `getHtmlCacheEntries()` | A dump: `{ key, bytes, status, stale, expiresIn, encodings }`. The HTML body is not returned, only its size. |
|
|
454
|
+
| `getHtmlCacheEntries()` | A dump: `{ key, bytes, status, stale, expiresIn, encodings, deps }`. The HTML body is not returned, only its size. |
|
|
455
|
+
|
|
456
|
+
### Targeted invalidation
|
|
457
|
+
|
|
458
|
+
`invalidateHtmlCache()` fills the gap between waiting for the TTL and flushing
|
|
459
|
+
the whole cache:
|
|
460
|
+
|
|
461
|
+
```js
|
|
462
|
+
import { invalidateHtmlCache } from "jskelet";
|
|
463
|
+
|
|
464
|
+
invalidateHtmlCache("/news/abc"); // that path and everything under it
|
|
465
|
+
invalidateHtmlCache("/news/:slug"); // the pattern syntax
|
|
466
|
+
invalidateHtmlCache([/-comments$/, "/"]); // regexps and lists
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
The default is to **stale** the entry, not to delete it: it is treated as
|
|
470
|
+
expired and falls through the normal stale-while-revalidate path. When a webhook
|
|
471
|
+
takes down five hundred pages at once, a hard delete starts five hundred cold
|
|
472
|
+
renders at exactly the moment the content changed, and hammers the upstream.
|
|
473
|
+
Staling instead hands the visitor the old HTML without a wait, and the refresh
|
|
474
|
+
runs in the background, once per key. Use `{ hard: true }` when the old HTML is
|
|
475
|
+
genuinely invalid.
|
|
476
|
+
|
|
477
|
+
Since the key is `path?query`, matching is done against the **path**: every
|
|
478
|
+
query variant of a path (including `?utm_source=…`) is covered by one call. For
|
|
479
|
+
a plain string the prefix stops at a segment boundary — a `/news` rule does not
|
|
480
|
+
touch `/newsletter`.
|
|
481
|
+
|
|
482
|
+
An in-flight render is targeted too: a pass that started before the purge is
|
|
483
|
+
carrying data that is now out of date, so it is **not** stored and the next
|
|
484
|
+
request starts a fresh pass.
|
|
485
|
+
|
|
486
|
+
### Automatic dependencies: `clearDataCache` refreshes the HTML too
|
|
487
|
+
|
|
488
|
+
You do not have to declare which page is affected by which content. Every
|
|
489
|
+
`withDataCache` key read during a render is recorded, and when `clearDataCache()`
|
|
490
|
+
drops a key, every HTML entry that **actually read it** is staled.
|
|
491
|
+
|
|
492
|
+
```js
|
|
493
|
+
// the "this article changed" webhook
|
|
494
|
+
clearDataCache(`news:${slug}`);
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
That single line refreshes the article page, the home page that lists it and the
|
|
498
|
+
tag page together — because all three read that key. The most common mistake in
|
|
499
|
+
manual tagging (marking the detail page and forgetting the listing) is
|
|
500
|
+
structurally impossible here: nothing is declared, everything is observed.
|
|
501
|
+
|
|
502
|
+
Details:
|
|
503
|
+
|
|
504
|
+
- Dependencies are collected **on every refresh**, since the keys a page reads
|
|
505
|
+
can change over time.
|
|
506
|
+
- A purge that lands while a render is in flight is caught as well: that pass
|
|
507
|
+
would be stale the moment it was born, so it is not stored.
|
|
508
|
+
- The dependency count per page shows up as `deps` in the `getHtmlCacheEntries()`
|
|
509
|
+
dump. If an invalidation is not refreshing the page you expected, look there
|
|
510
|
+
first: the page may not be reading that data through `withDataCache`.
|
|
511
|
+
- An application that does not use `withDataCache` has nothing to record;
|
|
512
|
+
tracking can be turned off entirely with `cache().trackDependencies: false`.
|
|
513
|
+
- Staled paths go to the **front** of the prewarm queue. If `prewarm` is set up
|
|
514
|
+
the page is refreshed without waiting for a visitor, and the pass summary says
|
|
515
|
+
so: `[prewarm] warmed 12/12 pages, 3 invalidated (0.4s)`.
|
|
453
516
|
|
|
454
517
|
To write an admin endpoint:
|
|
455
518
|
|
|
@@ -528,11 +591,13 @@ Rules:
|
|
|
528
591
|
1. The list is collected. If it is longer than `max` (400 by default) a slice is
|
|
529
592
|
selected: the paths matching `priority` are taken first **on every round**,
|
|
530
593
|
and the remaining slots are filled from the queue.
|
|
531
|
-
2. `concurrency` workers send requests in parallel (4 in prod,
|
|
532
|
-
|
|
594
|
+
2. `concurrency` workers send requests in parallel (4 in prod, 1 in dev). A
|
|
595
|
+
single worker in dev: so the scan does not compete for CPU with the render of
|
|
533
596
|
the page you currently have open in the browser.
|
|
534
597
|
3. If `rps` is given, the round never goes above that rate — no matter the
|
|
535
|
-
parallelism.
|
|
598
|
+
parallelism. In dev, 4 requests per second apply by default: rendering runs
|
|
599
|
+
on a single event loop, so an unpaced round leaves page requests and the dev
|
|
600
|
+
panel's live channel waiting behind it.
|
|
536
601
|
4. **A single serial retry round** is performed for the failed paths after
|
|
537
602
|
waiting `retryDelayMs` (`concurrency: 1`). The wait is deliberate: rate limit
|
|
538
603
|
windows are on the order of seconds, so retrying immediately just earns the
|
|
@@ -623,8 +688,8 @@ comes first so that one-off experiments can be done without editing the config.
|
|
|
623
688
|
| --- | --- | --- | --- |
|
|
624
689
|
| On/off | `PREWARM=0` disables it, `PREWARM=1` overrides the config and enables it | `enabled` | `true` |
|
|
625
690
|
| Maximum paths per round | `PREWARM_MAX` | `max` | `400` |
|
|
626
|
-
| Parallelism | `PREWARM_CONCURRENCY` | `concurrency` | prod 4, dev
|
|
627
|
-
| Requests per second | `PREWARM_RPS` | `rps` | `0` (unlimited) |
|
|
691
|
+
| Parallelism | `PREWARM_CONCURRENCY` | `concurrency` | prod 4, dev 1 |
|
|
692
|
+
| Requests per second | `PREWARM_RPS` | `rps` | prod `0` (unlimited), dev 4 |
|
|
628
693
|
| Startup delay (ms) | `PREWARM_DELAY_MS` | `delayMs` | prod 500, dev 3000 |
|
|
629
694
|
| Retry round delay (ms) | `PREWARM_RETRY_DELAY_MS` | `retryDelayMs` | `2000` |
|
|
630
695
|
| Period (seconds) | `PREWARM_INTERVAL_SECONDS` | `intervalSeconds` | `0` (off) |
|
|
@@ -572,9 +572,9 @@ Details: [03-routing.md](./03-routing.md).
|
|
|
572
572
|
## `cache()`
|
|
573
573
|
|
|
574
574
|
**Type:**
|
|
575
|
-
`() => { html?: Record<string, number>, maxEntries?: number, data?: object, trackUpstream?: boolean, transientRetry?: object | false, prewarm?: object }` —
|
|
575
|
+
`() => { html?: Record<string, number>, maxEntries?: number, data?: object, trackUpstream?: boolean, trackDependencies?: boolean, transientRetry?: object | false, prewarm?: object }` —
|
|
576
576
|
**Default:**
|
|
577
|
-
`{ html: {}, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, trackUpstream: true, transientRetry: { attempts: 1, delayMs: 300 }, prewarm: { enabled: true, max: 400, intervalSeconds: 0 } }`
|
|
577
|
+
`{ html: {}, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, trackUpstream: true, trackDependencies: true, transientRetry: { attempts: 1, delayMs: 300 }, prewarm: { enabled: true, max: 400, intervalSeconds: 0 } }`
|
|
578
578
|
|
|
579
579
|
### `cache().html`
|
|
580
580
|
|
|
@@ -622,6 +622,17 @@ When on, `globalThis.fetch` is wrapped and transient upstream failures (`429`,
|
|
|
622
622
|
`reportUpstreamFailure()` is not required. An application that wraps `fetch`
|
|
623
623
|
itself can turn this off.
|
|
624
624
|
|
|
625
|
+
### `cache().trackDependencies`
|
|
626
|
+
|
|
627
|
+
**Type:** `boolean` — **Default:** `true`
|
|
628
|
+
|
|
629
|
+
When on, the `withDataCache` keys a render reads are recorded, and
|
|
630
|
+
`clearDataCache()` also stales the HTML pages that read that data — targeted
|
|
631
|
+
invalidation without the application declaring anything
|
|
632
|
+
([06-caching.md](./06-caching.md)). An application that does not use
|
|
633
|
+
`withDataCache` has nothing to record; turning this off also removes the cost of
|
|
634
|
+
setting up the context.
|
|
635
|
+
|
|
625
636
|
### `cache().transientRetry`
|
|
626
637
|
|
|
627
638
|
**Type:** `{ attempts?: number, delayMs?: number } | false` —
|
|
@@ -638,8 +649,8 @@ a 404; if the retries are exhausted the response is an uncached 503. `false` or
|
|
|
638
649
|
| --- | --- | --- | --- |
|
|
639
650
|
| `enabled` | `boolean` | `true` | If `false`, no prewarming happens (can be overridden with `PREWARM=1`) |
|
|
640
651
|
| `max` | `number` | `400` | At most how many paths are prewarmed per pass |
|
|
641
|
-
| `concurrency` | `number` | prod 4, dev
|
|
642
|
-
| `rps` | `number` | `0
|
|
652
|
+
| `concurrency` | `number` | prod 4, dev 1 | Number of parallel workers |
|
|
653
|
+
| `rps` | `number` | prod `0`, dev 4 | At most how many prewarm requests per second; `0` is unlimited. This is the setting that protects the upstream quota. The default brake in dev keeps prewarming from holding page requests up. |
|
|
643
654
|
| `delayMs` | `number` | prod 500, dev 3000 | Delay of the first pass after startup |
|
|
644
655
|
| `retryDelayMs` | `number` | `2000` | How long to wait before the retry pass |
|
|
645
656
|
| `intervalSeconds` | `number` | `0` | If greater than 0, the pass repeats periodically |
|
|
@@ -758,7 +769,7 @@ and no warning is printed.
|
|
|
758
769
|
| `DEV_TOKEN` | `devGate`, `prewarm` | — | If set, every request without a token gets a 404. Prewarming carries the token as a cookie. [09](./09-dev-tools.md) |
|
|
759
770
|
| `PREWARM` | `startPrewarm` | — | `0` turns prewarming off; `1` overrides `enabled: false` in the config and turns it on |
|
|
760
771
|
| `PREWARM_MAX` | `prewarm` | `400` | At most how many paths are prewarmed |
|
|
761
|
-
| `PREWARM_CONCURRENCY` | `prewarm` | prod 4, dev
|
|
772
|
+
| `PREWARM_CONCURRENCY` | `prewarm` | prod 4, dev 1 | Number of parallel workers |
|
|
762
773
|
| `PREWARM_RPS` | `prewarm` | `0` | At most how many prewarm requests per second; `0` is unlimited |
|
|
763
774
|
| `PREWARM_DELAY_MS` | `startPrewarm` | prod 500, dev 3000 | Delay of the first pass |
|
|
764
775
|
| `PREWARM_RETRY_DELAY_MS` | `prewarm` | `2000` | The wait before the retry pass |
|
package/docs/en/09-dev-tools.md
CHANGED
|
@@ -116,7 +116,7 @@ If `JSKELET_VERBOSE=1` is set, all files are listed when more than one changed.
|
|
|
116
116
|
## CSS hot-swap and full reload
|
|
117
117
|
|
|
118
118
|
The dev server watches `.jskelet/manifest.json` and broadcasts events to the
|
|
119
|
-
browser over
|
|
119
|
+
browser over the live channel (`<devBasePath>/ws`). Since the manifest is
|
|
120
120
|
rewritten on every build round, change detection is done through the manifest.
|
|
121
121
|
|
|
122
122
|
| What changed | Behavior |
|
|
@@ -134,6 +134,30 @@ When the server restarts, the overlay figures it out from the **boot id**: every
|
|
|
134
134
|
process broadcasts a unique `boot` value, the overlay sees the change, shows the
|
|
135
135
|
"restarted" note and does not reset its own state.
|
|
136
136
|
|
|
137
|
+
## The live channel
|
|
138
|
+
|
|
139
|
+
Everything the overlay shows — statistics, live reload and CSS hot-swap events —
|
|
140
|
+
arrives over a single WebSocket (`<devBasePath>/ws`). The panel used to poll for
|
|
141
|
+
statistics every two seconds, so every open tab kept hitting the server even
|
|
142
|
+
while the panel was closed. Now the server pushes as things change: when a
|
|
143
|
+
request or an error is recorded (coalesced over 120 ms), and every two seconds so
|
|
144
|
+
the time-based fields (uptime, memory, the prewarm counter) stay fresh. The
|
|
145
|
+
heartbeat is deliberately independent of prewarming: if the channel's tempo
|
|
146
|
+
followed a background job, the panel would be tied to that job's rhythm. Nothing
|
|
147
|
+
is computed when no panel is connected.
|
|
148
|
+
|
|
149
|
+
The handshake happens on the HTTP `upgrade` event, and that event never reaches
|
|
150
|
+
the middleware chain, so the channel is attached straight to the server after
|
|
151
|
+
`listen` (`attachDevSocket`). The server side pulls in no dependency such as
|
|
152
|
+
`ws`: all it needs is to write server-to-client text frames and to answer the
|
|
153
|
+
client's ping/close frames.
|
|
154
|
+
|
|
155
|
+
If the socket cannot be opened at all (a proxy in between may not pass WebSocket
|
|
156
|
+
through), the overlay falls back to the old path: the `/events` SSE stream plus
|
|
157
|
+
polling `/stats`. If the socket opens and later drops — that is, the server is
|
|
158
|
+
restarting — it reconnects every half second and the indicator reads
|
|
159
|
+
"server restarting…" in the meantime.
|
|
160
|
+
|
|
137
161
|
## Devtools overlay
|
|
138
162
|
|
|
139
163
|
A floating bubble in the bottom right; opened with `Alt+D`, closed with `Esc` or
|
|
@@ -240,8 +264,9 @@ Under `brand.devBasePath` (default `/__jskelet/dev`):
|
|
|
240
264
|
| --- | --- | --- |
|
|
241
265
|
| `/overlay.js` | GET | The overlay script |
|
|
242
266
|
| `/logo.png` | GET | The overlay logo |
|
|
243
|
-
| `/
|
|
244
|
-
| `/
|
|
267
|
+
| `/ws` | GET (upgrade) | Live channel: statistics, live reload and CSS hot-swap events |
|
|
268
|
+
| `/events` | GET | SSE: the fallback event stream, used only when WebSocket cannot be established |
|
|
269
|
+
| `/stats` | GET | Current statistics; the data endpoint of that same fallback |
|
|
245
270
|
| `/report` | GET | The report page (HTML) |
|
|
246
271
|
| `/report.js` | GET | The report page's script |
|
|
247
272
|
| `/report/data` | GET | The report's single data source (JSON) |
|
|
@@ -301,7 +326,8 @@ redirect rules.
|
|
|
301
326
|
| Broken route module | Warn + skip | Throw |
|
|
302
327
|
| Devtools and report | Mounted | Never loaded |
|
|
303
328
|
| `globalThis.fetch` | Wrapped (measurement) | Untouched |
|
|
304
|
-
| Prewarm concurrency |
|
|
329
|
+
| Prewarm concurrency | 1 | 4 |
|
|
330
|
+
| Prewarm rate limit | 4 requests/second | Unlimited |
|
|
305
331
|
| Prewarm delay | 3000 ms | 500 ms |
|
|
306
332
|
| Missing icon warning | Emitted | Not emitted |
|
|
307
333
|
| Precompress | Does not run in watch | Runs |
|
package/docs/en/11-migration.md
CHANGED
|
@@ -53,7 +53,8 @@ will feel familiar. The *reasons* behind the differences are in
|
|
|
53
53
|
| `fetch(..., { next: { revalidate } })` | — | The cache is at page level |
|
|
54
54
|
| `unstable_cache` | — | No cross-request data cache; there is a page cache |
|
|
55
55
|
| React `cache()` | `cache()` | Same behavior: in-request memoization |
|
|
56
|
-
| `revalidatePath()` | `
|
|
56
|
+
| `revalidatePath()` | `invalidateHtmlCache("/news/:slug")` | A path, a pattern or a regexp; stales rather than deletes by default |
|
|
57
|
+
| `revalidateTag()` | `clearDataCache("news:")` | No tags to declare: the dependency is observed during the render ([06](./06-caching.md)) |
|
|
57
58
|
| `cookies()`, `headers()` | `ctx.req.headers`, `ctx.req.cookies`* | Direct access to the Express object |
|
|
58
59
|
| `dynamic = "force-dynamic"` | Not passing `revalidate` | Which means the cache is off |
|
|
59
60
|
|
|
@@ -97,8 +98,6 @@ Account for these from the start in your migration plan:
|
|
|
97
98
|
`viewTransition` smooths the transition
|
|
98
99
|
([07](./07-configuration.md)).
|
|
99
100
|
- **Server Actions.** Form submissions are ordinary `app.post(...)` handlers.
|
|
100
|
-
- **Per-path invalidation (`revalidatePath`).** For now there is clearing the
|
|
101
|
-
whole cache (`clearHtmlCache()`) or waiting for the TTL to expire.
|
|
102
101
|
- **Automatic image optimization (at request time).** Optimization happens at
|
|
103
102
|
build time and only covers local images under `public/`; remote images are
|
|
104
103
|
emitted as-is.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jskelet",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A framework that feels like no framework: Express 5 + EJS server rendering, vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|