jskelet 0.6.16 → 0.7.0
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 +10 -0
- package/docs/06-cache.md +33 -8
- package/docs/07-yapilandirma.md +23 -9
- package/docs/09-dev-araclari.md +8 -0
- package/docs/en/06-caching.md +33 -7
- package/docs/en/07-configuration.md +22 -8
- package/docs/en/09-dev-tools.md +8 -0
- package/package.json +1 -1
- package/src/client/devtools/overlay.js +155 -2
- package/src/config/defaults.js +8 -3
- package/src/config/index.js +37 -2
- package/src/server/dev/devtools.js +47 -1
- package/src/server/prewarm.js +5 -1
- package/types/config/defaults.d.ts +8 -3
- package/types/server/prewarm.d.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,16 @@ one is listed under a **Breaking** heading.
|
|
|
17
17
|
|
|
18
18
|
### Added
|
|
19
19
|
|
|
20
|
+
- Dev tools **Server** tab can clear the HTML cache for the open page, every
|
|
21
|
+
HTML entry, or the data cache. Live stats include HTML/data entry counts;
|
|
22
|
+
new endpoints under `devBasePath`: `POST /cache/clear-html`,
|
|
23
|
+
`/cache/clear-data`, and `/cache/invalidate`.
|
|
24
|
+
- On-demand HTML cache via `cache().prewarm: false`: visited pages still
|
|
25
|
+
write to L1 and Redis/disk L2 with the usual `html` TTLs, but classic
|
|
26
|
+
prewarm, `onVisit` link warming, and early-expiry HTTP refresh stay off
|
|
27
|
+
(`PREWARM=1` does not override). Mutually exclusive with
|
|
28
|
+
`hooks.prewarmPaths`. Resolved `prewarm.mode` is `"off"` | `"classic"` |
|
|
29
|
+
`"onVisit"`.
|
|
20
30
|
- When `build.assets.prefix` is set, the CSS build rewrites root-absolute
|
|
21
31
|
`url()` paths under `static.prefixes` (e.g. `/fonts/…`) to the CDN
|
|
22
32
|
prefix so they match the S3 key (`…/js/fonts/…`). Default upload
|
package/docs/06-cache.md
CHANGED
|
@@ -1173,10 +1173,34 @@ loopback yalnızca o host'un anahtarını ısıtır — locale sitelerinde
|
|
|
1173
1173
|
`origins: ["http://localhost", "http://tr.localhost"]` gibi çoklu origin
|
|
1174
1174
|
gerekir.
|
|
1175
1175
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1176
|
+
Üç yüzey **karşılıklı dışlayıcıdır**: klasik (`hooks.prewarmPaths` + alanlar),
|
|
1177
|
+
`onVisit` ve on-demand (`prewarm: false`). Birini seçmek diğerini config
|
|
1178
|
+
yüklenirken reddeder.
|
|
1179
|
+
|
|
1180
|
+
### Mod: on-demand — `prewarm: false`
|
|
1181
|
+
|
|
1182
|
+
Arka plan ısıtması yok; yalnızca gerçek trafikle gelen sayfa önbelleğe yazılır.
|
|
1183
|
+
TTL `cache().html` (veya route `revalidate`) ile verilir. Redis açıksa L2'ye,
|
|
1184
|
+
değilse `.jskelet/cache/<buildId>/` disk L2'ye yazılır — ziyaret yazımı zaten
|
|
1185
|
+
böyle çalışır; bu mod klasik turu, `onVisit` link crawl'ını ve TTL öncesi
|
|
1186
|
+
early-expiry HTTP rewarm'ı kapatır. `PREWARM=1` bunu ezmez.
|
|
1187
|
+
|
|
1188
|
+
```js
|
|
1189
|
+
// jskelet.config.mjs
|
|
1190
|
+
export default {
|
|
1191
|
+
cache() {
|
|
1192
|
+
return {
|
|
1193
|
+
html: { "/": 60, "/haber/:slug": 300 },
|
|
1194
|
+
redis: { enabled: true, url: process.env.REDIS_URL },
|
|
1195
|
+
prewarm: false,
|
|
1196
|
+
};
|
|
1197
|
+
},
|
|
1198
|
+
};
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
`hooks.prewarmPaths` ile birlikte **yasaktır**. `prewarm: { enabled: false }`
|
|
1202
|
+
farklıdır: klasik turu keser ama early-expiry drain açık kalır; tamamen pasif
|
|
1203
|
+
mod için `prewarm: false` kullanın.
|
|
1180
1204
|
|
|
1181
1205
|
### Mod: `onVisit` — ziyaret edilen sayfanın linkleri
|
|
1182
1206
|
|
|
@@ -1442,12 +1466,13 @@ turunun gerçekten `MISS` → önbellek doldurup doldurmadığını buradan gör
|
|
|
1442
1466
|
`cache().vary.host: true` yoksa ilk locale'in HTML'i diğer host'a servis
|
|
1443
1467
|
edilir. Prewarm yalnızca `127.0.0.1` ile ısınıyorsa `prewarm.origins` ile
|
|
1444
1468
|
locale host'larını ekleyin.
|
|
1445
|
-
- **Isıtma hiç çalışmıyor.**
|
|
1446
|
-
|
|
1469
|
+
- **Isıtma hiç çalışmıyor.** `prewarm: false` (on-demand) bilinçli olarak
|
|
1470
|
+
hiçbir arka plan ısıtması kurmaz. Klasik modda `hooks.prewarmPaths` tanımlı
|
|
1471
|
+
değil, `PREWARM=0` ayarlı ya da `cache().prewarm.enabled === false`. `onVisit`
|
|
1447
1472
|
modunda `listen` sonrası logda `onVisit mode` satırını ve public cache'li
|
|
1448
1473
|
bir sayfa gezildiğini doğrulayın.
|
|
1449
|
-
- **Config `onVisit` + `max` / `prewarmPaths` ile düşüyor.**
|
|
1450
|
-
dışlayıcı; yalnızca birini kullanın.
|
|
1474
|
+
- **Config `onVisit` / `prewarm: false` + `max` / `prewarmPaths` ile düşüyor.**
|
|
1475
|
+
Üç yüzey karşılıklı dışlayıcı; yalnızca birini kullanın.
|
|
1451
1476
|
- **Isıtma turu API'yi 429'a sokuyor.** `rps` verilmemiş. `concurrency`
|
|
1452
1477
|
düşürmek yeterli değil; kotayı koruyan ayar toplam hız. Kalıcı çözüm veri
|
|
1453
1478
|
önbelleği: ikinci turdan sonra ısıtma upstream'e gitmez.
|
package/docs/07-yapilandirma.md
CHANGED
|
@@ -770,9 +770,9 @@ Ayrıntı: [03-routing.md](./03-routing.md).
|
|
|
770
770
|
## `cache()`
|
|
771
771
|
|
|
772
772
|
**Tip:**
|
|
773
|
-
`() => { html?: Record<string, number>, staleWhileRevalidate?: number, query?: Record<string, string[] | true>, vary?: { host?: boolean, headers?: string[], fn?: (req) => string | null }, maxEntries?: number, data?: object, trackUpstream?: boolean, trackDependencies?: boolean, transientRetry?: object | false, upstream?: object, redis?: object, prewarm?: object }` —
|
|
773
|
+
`() => { html?: Record<string, number>, staleWhileRevalidate?: number, query?: Record<string, string[] | true>, vary?: { host?: boolean, headers?: string[], fn?: (req) => string | null }, maxEntries?: number, data?: object, trackUpstream?: boolean, trackDependencies?: boolean, transientRetry?: object | false, upstream?: object, redis?: object, prewarm?: false | object }` —
|
|
774
774
|
**Varsayılan:**
|
|
775
|
-
`{ html: {}, staleWhileRevalidate: 60, query: {}, vary: { host: false }, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, trackUpstream: true, trackDependencies: true, transientRetry: { attempts: 1, delayMs: 300 }, upstream: { rate: 0 }, redis: { enabled: false }, prewarm: { enabled: true, max: 400, intervalSeconds: 0, origins: [] } }`
|
|
775
|
+
`{ html: {}, staleWhileRevalidate: 60, query: {}, vary: { host: false }, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, trackUpstream: true, trackDependencies: true, transientRetry: { attempts: 1, delayMs: 300 }, upstream: { rate: 0 }, redis: { enabled: false }, prewarm: { enabled: true, mode: "classic", max: 400, intervalSeconds: 0, origins: [] } }` (veya `prewarm: false` → `mode: "off"`)
|
|
776
776
|
|
|
777
777
|
### `cache().html`
|
|
778
778
|
|
|
@@ -1084,14 +1084,28 @@ tam cevabı olmadığı — [06-cache.md](./06-cache.md) içinde.
|
|
|
1084
1084
|
|
|
1085
1085
|
### `cache().prewarm`
|
|
1086
1086
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1087
|
+
Üç yüzey: **`false`** (on-demand — yalnızca ziyaret yazımı), **klasik** (liste
|
|
1088
|
+
+ açılış turu) veya **`onVisit`** (ziyaret edilen sayfadaki linkler). Birlikte
|
|
1089
|
+
verilemez — config yüklenirken hata. Çözümlenen `prewarm.mode`: `"off"` |
|
|
1090
|
+
`"classic"` | `"onVisit"`.
|
|
1091
|
+
|
|
1092
|
+
#### On-demand — `prewarm: false`
|
|
1093
|
+
|
|
1094
|
+
Arka plan ısıtması yok (klasik tur, `onVisit`, early-expiry HTTP rewarm).
|
|
1095
|
+
Ziyaret edilen sayfa `html` TTL'i ile L1 + Redis/disk L2'ye yazılır.
|
|
1096
|
+
`PREWARM=1` bunu ezmez. `hooks.prewarmPaths` ile **yasak**.
|
|
1097
|
+
`{ enabled: false }` klasik turu keser ama early-expiry açık kalır — tamamen
|
|
1098
|
+
pasif mod için `false` kullanın.
|
|
1099
|
+
|
|
1100
|
+
```js
|
|
1101
|
+
prewarm: false,
|
|
1102
|
+
```
|
|
1089
1103
|
|
|
1090
1104
|
#### Klasik alanlar
|
|
1091
1105
|
|
|
1092
1106
|
| Alan | Tip | Varsayılan | Anlamı |
|
|
1093
1107
|
| --- | --- | --- | --- |
|
|
1094
|
-
| `enabled` | `boolean` | `true` | `false` ise
|
|
1108
|
+
| `enabled` | `boolean` | `true` | `false` ise klasik tur yapılmaz (`PREWARM=1` ile ezilebilir); early-expiry drain açık kalır |
|
|
1095
1109
|
| `max` | `number` | `400` | Bir turda en fazla kaç yol ısıtılır |
|
|
1096
1110
|
| `concurrency` | `number` | prod 4, dev 1 | Paralel işçi sayısı |
|
|
1097
1111
|
| `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. |
|
|
@@ -1135,8 +1149,8 @@ prewarm: {
|
|
|
1135
1149
|
}
|
|
1136
1150
|
```
|
|
1137
1151
|
|
|
1138
|
-
`hooks.prewarmPaths` ve klasik alanlar (`max`, `priority`, …) `onVisit`
|
|
1139
|
-
**yasaktır**. Ayrıntı: [06-cache.md](./06-cache.md).
|
|
1152
|
+
`hooks.prewarmPaths` ve klasik alanlar (`max`, `priority`, …) `onVisit` veya
|
|
1153
|
+
`prewarm: false` ile **yasaktır**. Ayrıntı: [06-cache.md](./06-cache.md).
|
|
1140
1154
|
|
|
1141
1155
|
Sayısal alanların her biri aynı adı taşıyan ortam değişkeniyle ezilebilir; env
|
|
1142
1156
|
önceliklidir. Ayrıntı: [06-cache.md](./06-cache.md).
|
|
@@ -1154,7 +1168,7 @@ varsayılanına döner ve uyarır — sayfa düşmez.
|
|
|
1154
1168
|
| `layoutContext` | `({ pathname, metadata }) => object` | Layout local'leri; `lang`, `structuredData`, `extraHead`, `bodyClass` özel yorumlanır | [04](./04-render-ve-sablonlar.md) |
|
|
1155
1169
|
| `notFound` | `() => object \| null` | 404 sayfa tanımı; `null` ise framework'ün hata sayfası | [03](./03-routing.md) |
|
|
1156
1170
|
| `error` | `({ status, error }) => object \| string \| null` | 404 dışındaki hata sayfaları (ve `notFound` yoksa 404); sayfa tanımı ya da doğrudan HTML | [03](./03-routing.md) |
|
|
1157
|
-
| `prewarmPaths` | `() => string[]` | Klasik ısıtmada ısıtılacak yollar; tanımlı değilse klasik tur kurulmaz. `onVisit` ile
|
|
1171
|
+
| `prewarmPaths` | `() => string[]` | Klasik ısıtmada ısıtılacak yollar; tanımlı değilse klasik tur kurulmaz. `onVisit` ve `prewarm: false` ile **yasak** | [06](./06-cache.md) |
|
|
1158
1172
|
|
|
1159
1173
|
```js
|
|
1160
1174
|
hooks: {
|
|
@@ -1247,7 +1261,7 @@ basılmaz.
|
|
|
1247
1261
|
| `JSKELET_CLOUDFLARE_KEY` | Cloudflare cache yüzeyi | — | API token. Verilene kadar CDN purge'ü ve edge analitiği kapalıdır; config'teki `apiToken`'ı ezer. Token hiçbir cevapta dönmez. [06](./06-cache.md) |
|
|
1248
1262
|
| `JSKELET_CLOUDFLARE_ZONE_ID` | Cloudflare cache yüzeyi | — | Zone kimliği. Token'la birlikte verilmedikçe hiçbir Cloudflare ucu çağrılmaz |
|
|
1249
1263
|
| `JSKELET_CLOUDFLARE_HOSTNAME` | Cloudflare cache yüzeyi | — | Purge URL'lerinin kökü. Panel iç bir adresten açılıyorsa gerekir |
|
|
1250
|
-
| `PREWARM` | `startPrewarm` | — | `0` ısıtmayı kapatır; `1` config'teki `enabled: false`'u ezip açar |
|
|
1264
|
+
| `PREWARM` | `startPrewarm` | — | `0` ısıtmayı kapatır; `1` config'teki `enabled: false`'u ezip açar. `prewarm: false` (on-demand) ezilmez |
|
|
1251
1265
|
| `PREWARM_MAX` | `prewarm` | `400` | En fazla kaç yol ısıtılır |
|
|
1252
1266
|
| `PREWARM_CONCURRENCY` | `prewarm` | prod 4, dev 1 | Paralel işçi sayısı |
|
|
1253
1267
|
| `PREWARM_RPS` | `prewarm` | `0` | Saniyedeki en fazla ısıtma isteği; `0` sınırsız |
|
package/docs/09-dev-araclari.md
CHANGED
|
@@ -208,6 +208,11 @@ Gösterdikleri:
|
|
|
208
208
|
bloke süresi.
|
|
209
209
|
- **Prewarm:** ısıtma turunun ilerlemesi; panelden elle tetiklenebilir, tek tek
|
|
210
210
|
yollar tekrar denenebilir.
|
|
211
|
+
- **Önbellek (Server sekmesi):** HTML ve veri girdisi sayıları; **Clear this
|
|
212
|
+
page** açık yolun bütün varyantlarını sert düşürüp sayfayı yeniler, **Clear
|
|
213
|
+
all HTML** / **Clear data cache** süreç önbelleğini boşaltır (onay ister).
|
|
214
|
+
Aynı işlemler admin panelindeki `html:invalidate` / `html:clear` /
|
|
215
|
+
`data:clear` ile paylaşılır.
|
|
211
216
|
- **Süreç:** pid, Node sürümü, uptime, RSS ve heap kullanımı.
|
|
212
217
|
- **Sürüm:** kurulu JSkelet sürümü ve npm'deki `latest` ile karşılaştırması.
|
|
213
218
|
Yeni bir sürüm varsa **Server** sekmesinde `update` rozeti ve yükseltme
|
|
@@ -297,6 +302,9 @@ Rapor katmanı yalnızca development'ta yüklenir, üretim çıktısına hiç gi
|
|
|
297
302
|
| `/report/clear` | POST | Sayfa ölçümlerini ve sunucu API kayıtlarını sıfırlar |
|
|
298
303
|
| `/prewarm` | POST | Isıtmayı elle tetikler. Gövdede `paths` varsa yalnızca o yollar; ısıtma zaten çalışıyorsa 409. |
|
|
299
304
|
| `/clear` | POST | İstek ve hata günlüklerini sıfırlar |
|
|
305
|
+
| `/cache/clear-html` | POST | Tüm HTML önbelleğini boşaltır; `{ cleared }` döner |
|
|
306
|
+
| `/cache/clear-data` | POST | Veri önbelleğini boşaltır. Gövdede isteğe bağlı `prefix`; `{ cleared }` döner |
|
|
307
|
+
| `/cache/invalidate` | POST | Gövdede `path` (zorunlu, `/` ile başlamalı) ve isteğe bağlı `hard` (varsayılan `true`). Etkilenen girdi sayısıyla `{ count }` döner |
|
|
300
308
|
|
|
301
309
|
Bu uçların tamamı `mountDevtools()` tarafından yalnızca
|
|
302
310
|
`NODE_ENV=development` iken bağlanır; prod sürecine dinamik import sayesinde
|
package/docs/en/06-caching.md
CHANGED
|
@@ -1175,10 +1175,35 @@ traffic. With `vary.host`, the default loopback only warms that host's key —
|
|
|
1175
1175
|
locale sites need multiple origins such as
|
|
1176
1176
|
`origins: ["http://localhost", "http://tr.localhost"]`.
|
|
1177
1177
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1178
|
+
Three surfaces are **mutually exclusive**: classic (`hooks.prewarmPaths` +
|
|
1179
|
+
fields), `onVisit`, and on-demand (`prewarm: false`). Choosing one rejects the
|
|
1180
|
+
others at config load.
|
|
1181
|
+
|
|
1182
|
+
### Mode: on-demand — `prewarm: false`
|
|
1183
|
+
|
|
1184
|
+
No background warming; only pages hit by real traffic are written to the cache.
|
|
1185
|
+
TTL comes from `cache().html` (or the route `revalidate`). With Redis on, the
|
|
1186
|
+
entry goes to L2 Redis; otherwise to the `.jskelet/cache/<buildId>/` disk L2 —
|
|
1187
|
+
visit writes already work that way; this mode turns off the classic pass,
|
|
1188
|
+
`onVisit` link crawling, and early-expiry HTTP refresh before TTL. `PREWARM=1`
|
|
1189
|
+
does not override it.
|
|
1190
|
+
|
|
1191
|
+
```js
|
|
1192
|
+
// jskelet.config.mjs
|
|
1193
|
+
export default {
|
|
1194
|
+
cache() {
|
|
1195
|
+
return {
|
|
1196
|
+
html: { "/": 60, "/news/:slug": 300 },
|
|
1197
|
+
redis: { enabled: true, url: process.env.REDIS_URL },
|
|
1198
|
+
prewarm: false,
|
|
1199
|
+
};
|
|
1200
|
+
},
|
|
1201
|
+
};
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
**Forbidden** together with `hooks.prewarmPaths`. `prewarm: { enabled: false }`
|
|
1205
|
+
is different: it stops the classic pass but leaves the early-expiry drain on —
|
|
1206
|
+
use `prewarm: false` for fully passive mode.
|
|
1182
1207
|
|
|
1183
1208
|
### Mode: `onVisit` — links from the page just visited
|
|
1184
1209
|
|
|
@@ -1449,12 +1474,13 @@ filled the cache.
|
|
|
1449
1474
|
without `cache().vary.host: true` the first locale's HTML is served to the
|
|
1450
1475
|
other host. If prewarm only hits `127.0.0.1`, add the locale hosts via
|
|
1451
1476
|
`prewarm.origins`.
|
|
1452
|
-
- **The warm-up never runs.**
|
|
1477
|
+
- **The warm-up never runs.** `prewarm: false` (on-demand) intentionally
|
|
1478
|
+
installs no background warming. In classic mode `hooks.prewarmPaths` is not
|
|
1453
1479
|
defined, `PREWARM=0` is set, or `cache().prewarm.enabled === false`. In
|
|
1454
1480
|
`onVisit` mode check the `onVisit mode` log line after `listen` and that a
|
|
1455
1481
|
public cacheable page was visited.
|
|
1456
|
-
- **Config fails with `onVisit` + `max` / `prewarmPaths`.**
|
|
1457
|
-
mutually exclusive; use only one.
|
|
1482
|
+
- **Config fails with `onVisit` / `prewarm: false` + `max` / `prewarmPaths`.**
|
|
1483
|
+
The three surfaces are mutually exclusive; use only one.
|
|
1458
1484
|
- **The warm-up round pushes the API into 429.** No `rps` was given. Lowering
|
|
1459
1485
|
`concurrency` is not enough; the setting that protects the quota is the total
|
|
1460
1486
|
rate. The lasting fix is the data cache: after the second round the warm-up
|
|
@@ -784,9 +784,9 @@ Details: [03-routing.md](./03-routing.md).
|
|
|
784
784
|
## `cache()`
|
|
785
785
|
|
|
786
786
|
**Type:**
|
|
787
|
-
`() => { html?: Record<string, number>, staleWhileRevalidate?: number, query?: Record<string, string[] | true>, vary?: { host?: boolean, headers?: string[], fn?: (req) => string | null }, maxEntries?: number, data?: object, trackUpstream?: boolean, trackDependencies?: boolean, transientRetry?: object | false, upstream?: object, redis?: object, prewarm?: object }` —
|
|
787
|
+
`() => { html?: Record<string, number>, staleWhileRevalidate?: number, query?: Record<string, string[] | true>, vary?: { host?: boolean, headers?: string[], fn?: (req) => string | null }, maxEntries?: number, data?: object, trackUpstream?: boolean, trackDependencies?: boolean, transientRetry?: object | false, upstream?: object, redis?: object, prewarm?: false | object }` —
|
|
788
788
|
**Default:**
|
|
789
|
-
`{ html: {}, staleWhileRevalidate: 60, query: {}, vary: { host: false }, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, trackUpstream: true, trackDependencies: true, transientRetry: { attempts: 1, delayMs: 300 }, upstream: { rate: 0 }, redis: { enabled: false }, prewarm: { enabled: true, max: 400, intervalSeconds: 0, origins: [] } }`
|
|
789
|
+
`{ html: {}, staleWhileRevalidate: 60, query: {}, vary: { host: false }, maxEntries: 500, data: { maxEntries: 10000, staleFactor: 10 }, trackUpstream: true, trackDependencies: true, transientRetry: { attempts: 1, delayMs: 300 }, upstream: { rate: 0 }, redis: { enabled: false }, prewarm: { enabled: true, mode: "classic", max: 400, intervalSeconds: 0, origins: [] } }` (or `prewarm: false` → `mode: "off"`)
|
|
790
790
|
|
|
791
791
|
### `cache().html`
|
|
792
792
|
|
|
@@ -1106,14 +1106,28 @@ edges hold this page" has no exact answer — is in
|
|
|
1106
1106
|
|
|
1107
1107
|
### `cache().prewarm`
|
|
1108
1108
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1109
|
+
Three surfaces: **`false`** (on-demand — visit writes only), **classic** (list
|
|
1110
|
+
+ startup pass), or **`onVisit`** (links from the page just visited). They
|
|
1111
|
+
cannot be combined — config load throws. Resolved `prewarm.mode`: `"off"` |
|
|
1112
|
+
`"classic"` | `"onVisit"`.
|
|
1113
|
+
|
|
1114
|
+
#### On-demand — `prewarm: false`
|
|
1115
|
+
|
|
1116
|
+
No background warming (classic pass, `onVisit`, early-expiry HTTP refresh).
|
|
1117
|
+
Visited pages still write to L1 + Redis/disk L2 with the `html` TTLs.
|
|
1118
|
+
`PREWARM=1` does not override. **Forbidden** with `hooks.prewarmPaths`.
|
|
1119
|
+
`{ enabled: false }` stops the classic pass but leaves early-expiry on — use
|
|
1120
|
+
`false` for fully passive mode.
|
|
1121
|
+
|
|
1122
|
+
```js
|
|
1123
|
+
prewarm: false,
|
|
1124
|
+
```
|
|
1111
1125
|
|
|
1112
1126
|
#### Classic fields
|
|
1113
1127
|
|
|
1114
1128
|
| Field | Type | Default | Meaning |
|
|
1115
1129
|
| --- | --- | --- | --- |
|
|
1116
|
-
| `enabled` | `boolean` | `true` | If `false`, no
|
|
1130
|
+
| `enabled` | `boolean` | `true` | If `false`, no classic pass (`PREWARM=1` can override); early-expiry drain stays on |
|
|
1117
1131
|
| `max` | `number` | `400` | At most how many paths are prewarmed per pass |
|
|
1118
1132
|
| `concurrency` | `number` | prod 4, dev 1 | Number of parallel workers |
|
|
1119
1133
|
| `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. |
|
|
@@ -1158,7 +1172,7 @@ prewarm: {
|
|
|
1158
1172
|
```
|
|
1159
1173
|
|
|
1160
1174
|
`hooks.prewarmPaths` and classic fields (`max`, `priority`, …) are **forbidden**
|
|
1161
|
-
with `onVisit`. Details: [06-caching.md](./06-caching.md).
|
|
1175
|
+
with `onVisit` or `prewarm: false`. Details: [06-caching.md](./06-caching.md).
|
|
1162
1176
|
|
|
1163
1177
|
Each numeric field can be overridden by an environment variable of the same
|
|
1164
1178
|
name; env takes precedence. Details: [06-caching.md](./06-caching.md).
|
|
@@ -1176,7 +1190,7 @@ its own default and warns — the page does not go down.
|
|
|
1176
1190
|
| `layoutContext` | `({ pathname, metadata }) => object` | Layout locals; `lang`, `structuredData`, `extraHead` and `bodyClass` get special treatment | [04](./04-rendering.md) |
|
|
1177
1191
|
| `notFound` | `() => object \| null` | 404 page definition; if `null`, the framework's error page | [03](./03-routing.md) |
|
|
1178
1192
|
| `error` | `({ status, error }) => object \| string \| null` | Error pages other than 404 (and 404 when there is no `notFound`); a page definition or HTML directly | [03](./03-routing.md) |
|
|
1179
|
-
| `prewarmPaths` | `() => string[]` | Paths for classic prewarm; if omitted, the classic pass is never set up. **Forbidden** with `onVisit` | [06](./06-caching.md) |
|
|
1193
|
+
| `prewarmPaths` | `() => string[]` | Paths for classic prewarm; if omitted, the classic pass is never set up. **Forbidden** with `onVisit` and `prewarm: false` | [06](./06-caching.md) |
|
|
1180
1194
|
|
|
1181
1195
|
```js
|
|
1182
1196
|
hooks: {
|
|
@@ -1270,7 +1284,7 @@ and no warning is printed.
|
|
|
1270
1284
|
| `JSKELET_CLOUDFLARE_KEY` | Cloudflare cache surface | — | API token. Until it is set, CDN purging and edge analytics stay off; it overrides `apiToken` in the config. The token is never returned in a response. [06](./06-caching.md) |
|
|
1271
1285
|
| `JSKELET_CLOUDFLARE_ZONE_ID` | Cloudflare cache surface | — | Zone identifier. No Cloudflare endpoint is called unless it is set alongside the token |
|
|
1272
1286
|
| `JSKELET_CLOUDFLARE_HOSTNAME` | Cloudflare cache surface | — | The root for purge URLs. Required when the panel is opened over an internal address |
|
|
1273
|
-
| `PREWARM` | `startPrewarm` | — | `0` turns prewarming off; `1` overrides `enabled: false` in the config and turns it on |
|
|
1287
|
+
| `PREWARM` | `startPrewarm` | — | `0` turns prewarming off; `1` overrides `enabled: false` in the config and turns it on. Does not override `prewarm: false` (on-demand) |
|
|
1274
1288
|
| `PREWARM_MAX` | `prewarm` | `400` | At most how many paths are prewarmed |
|
|
1275
1289
|
| `PREWARM_CONCURRENCY` | `prewarm` | prod 4, dev 1 | Number of parallel workers |
|
|
1276
1290
|
| `PREWARM_RPS` | `prewarm` | `0` | At most how many prewarm requests per second; `0` is unlimited |
|
package/docs/en/09-dev-tools.md
CHANGED
|
@@ -213,6 +213,11 @@ What it shows:
|
|
|
213
213
|
blocking time.
|
|
214
214
|
- **Prewarm:** the progress of the warming round; it can be triggered manually
|
|
215
215
|
from the panel, and individual paths can be retried.
|
|
216
|
+
- **Cache (Server tab):** HTML and data entry counts; **Clear this page**
|
|
217
|
+
hard-drops every variant of the open path and reloads, **Clear all HTML** /
|
|
218
|
+
**Clear data cache** empty the in-process caches (with a confirm). Same
|
|
219
|
+
core APIs as the admin panel's `html:invalidate` / `html:clear` /
|
|
220
|
+
`data:clear`.
|
|
216
221
|
- **Process:** pid, Node version, uptime, RSS and heap usage.
|
|
217
222
|
- **Version:** the installed JSkelet version compared against the `latest` tag
|
|
218
223
|
on npm. When a newer release exists, the **Server** tab grows an `update` chip
|
|
@@ -302,6 +307,9 @@ Under `brand.devBasePath` (default `/__jskelet/dev`):
|
|
|
302
307
|
| `/report/clear` | POST | Resets page measurements and server API records |
|
|
303
308
|
| `/prewarm` | POST | Triggers warming manually. If the body has `paths`, only those paths; 409 if warming is already running. |
|
|
304
309
|
| `/clear` | POST | Resets the request and error logs |
|
|
310
|
+
| `/cache/clear-html` | POST | Clears the entire HTML cache; returns `{ cleared }` |
|
|
311
|
+
| `/cache/clear-data` | POST | Clears the data cache. Optional `prefix` in the body; returns `{ cleared }` |
|
|
312
|
+
| `/cache/invalidate` | POST | Body: required `path` (must start with `/`) and optional `hard` (default `true`). Returns `{ count }` for affected entries |
|
|
305
313
|
|
|
306
314
|
All of these endpoints are mounted by `mountDevtools()` only when
|
|
307
315
|
`NODE_ENV=development`; thanks to the dynamic import, nothing is loaded into the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jskelet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A framework that feels like no framework: Express 5 + build-time .jsk SSR (optional EJS peer), vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -861,6 +861,93 @@ async function clearAll() {
|
|
|
861
861
|
pollServer();
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
+
/** Son önbellek eyleminin kısa geri bildirimi; sonraki poll'da silinmez. */
|
|
865
|
+
let cacheNotice = null;
|
|
866
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
867
|
+
let cacheNoticeTimer = null;
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* @param {string} message
|
|
871
|
+
*/
|
|
872
|
+
function setCacheNotice(message) {
|
|
873
|
+
cacheNotice = message;
|
|
874
|
+
if (cacheNoticeTimer) clearTimeout(cacheNoticeTimer);
|
|
875
|
+
cacheNoticeTimer = setTimeout(() => {
|
|
876
|
+
cacheNotice = null;
|
|
877
|
+
cacheNoticeTimer = null;
|
|
878
|
+
lastBody = "";
|
|
879
|
+
render();
|
|
880
|
+
}, 4000);
|
|
881
|
+
lastBody = "";
|
|
882
|
+
render();
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Bu sekmenin HTML önbelleğini düşürür ve sayfayı yeniler — aksi hâlde
|
|
887
|
+
* tarayıcıdaki DOM eski kalır, soğuk render görülmez.
|
|
888
|
+
*/
|
|
889
|
+
async function clearThisPageCache() {
|
|
890
|
+
const path = location.pathname || "/";
|
|
891
|
+
try {
|
|
892
|
+
await fetch(`${BASE}/cache/invalidate`, {
|
|
893
|
+
method: "POST",
|
|
894
|
+
headers: { "content-type": "application/json" },
|
|
895
|
+
body: JSON.stringify({ path, hard: true }),
|
|
896
|
+
});
|
|
897
|
+
} catch {
|
|
898
|
+
setCacheNotice("Could not reach the server to clear this page.");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
saveState();
|
|
902
|
+
location.reload();
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
async function clearHtmlCacheAll() {
|
|
906
|
+
if (
|
|
907
|
+
!confirm(
|
|
908
|
+
"Clear the entire HTML cache? Every page goes cold until the next render or prewarm.",
|
|
909
|
+
)
|
|
910
|
+
) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
try {
|
|
915
|
+
const response = await fetch(`${BASE}/cache/clear-html`, { method: "POST" });
|
|
916
|
+
const body = await response.json().catch(() => ({}));
|
|
917
|
+
setCacheNotice(
|
|
918
|
+
`HTML cache cleared (${typeof body.cleared === "number" ? body.cleared : "?"} entries).`,
|
|
919
|
+
);
|
|
920
|
+
} catch {
|
|
921
|
+
setCacheNotice("Could not clear the HTML cache.");
|
|
922
|
+
}
|
|
923
|
+
pollServer();
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async function clearDataCacheAll() {
|
|
927
|
+
if (
|
|
928
|
+
!confirm(
|
|
929
|
+
"Clear every data-cache entry? Upstream traffic will spike on the next renders.",
|
|
930
|
+
)
|
|
931
|
+
) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
try {
|
|
936
|
+
const response = await fetch(`${BASE}/cache/clear-data`, {
|
|
937
|
+
method: "POST",
|
|
938
|
+
headers: { "content-type": "application/json" },
|
|
939
|
+
body: JSON.stringify({}),
|
|
940
|
+
});
|
|
941
|
+
const body = await response.json().catch(() => ({}));
|
|
942
|
+
setCacheNotice(
|
|
943
|
+
`Data cache cleared (${typeof body.cleared === "number" ? body.cleared : "?"} entries).`,
|
|
944
|
+
);
|
|
945
|
+
} catch {
|
|
946
|
+
setCacheNotice("Could not clear the data cache.");
|
|
947
|
+
}
|
|
948
|
+
pollServer();
|
|
949
|
+
}
|
|
950
|
+
|
|
864
951
|
/* ---------------------------------------------------------------- arayüz */
|
|
865
952
|
|
|
866
953
|
const CSS = `
|
|
@@ -1162,6 +1249,7 @@ h4 .head-action { float: inline-end; text-transform: none; letter-spacing: 0; }
|
|
|
1162
1249
|
.status.warn { background: rgba(251,191,36,.14); color: var(--mid); }
|
|
1163
1250
|
.status.bad { background: rgba(251,113,133,.14); color: var(--bad); }
|
|
1164
1251
|
.hint { color: var(--muted); font-size: 10.5px; }
|
|
1252
|
+
.hint.block { display: block; margin: 0 0 8px; line-height: 1.45; }
|
|
1165
1253
|
.row .good { color: var(--good); } .row .mid { color: var(--mid); } .row .bad { color: var(--bad); }
|
|
1166
1254
|
.row.clickable { cursor: pointer; grid-template-columns: auto auto 1fr auto auto; }
|
|
1167
1255
|
.row.clickable.selected { border-color: rgba(255,255,255,.22); background: var(--surface-hover); }
|
|
@@ -1200,9 +1288,14 @@ h4 .head-action { float: inline-end; text-transform: none; letter-spacing: 0; }
|
|
|
1200
1288
|
.btn.mini { display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px; font-size: 11px; }
|
|
1201
1289
|
.btn.mini svg { width: 12px; height: 12px; }
|
|
1202
1290
|
.btn[disabled] { opacity: .5; cursor: default; }
|
|
1203
|
-
.toolbar { display: flex; justify-content: flex-end; margin-bottom: 10px; }
|
|
1291
|
+
.toolbar { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; margin-bottom: 10px; }
|
|
1292
|
+
.toolbar.start { justify-content: flex-start; }
|
|
1204
1293
|
.btn.danger { color: #ffc9d2; border-color: rgba(251,113,133,.28); background: rgba(251,113,133,.1); }
|
|
1205
1294
|
.btn.danger:hover { color: #fff; border-color: rgba(251,113,133,.55); background: rgba(251,113,133,.22); }
|
|
1295
|
+
.notice {
|
|
1296
|
+
margin: 0 0 10px; padding: 8px 10px; border-radius: 8px; font-size: 12px; line-height: 1.4;
|
|
1297
|
+
color: #b9f2dc; background: rgba(52,211,153,.1); border: 1px solid rgba(52,211,153,.28);
|
|
1298
|
+
}
|
|
1206
1299
|
`;
|
|
1207
1300
|
|
|
1208
1301
|
let shadow = null;
|
|
@@ -2022,6 +2115,14 @@ function serverTab() {
|
|
|
2022
2115
|
{ label: "Avg. render", value: ms(average), tone: grade(average, [150, 500]) },
|
|
2023
2116
|
{ label: "Slowest", value: ms(slowest), tone: grade(slowest, [300, 1000]) },
|
|
2024
2117
|
{ label: "Restart", value: String(restarts), tone: offline ? "mid" : "" },
|
|
2118
|
+
{
|
|
2119
|
+
label: "HTML cache",
|
|
2120
|
+
value: String(serverStats.cache?.html ?? 0),
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
label: "Data cache",
|
|
2124
|
+
value: String(serverStats.cache?.data ?? 0),
|
|
2125
|
+
},
|
|
2025
2126
|
...(serverStats.prewarm?.total
|
|
2026
2127
|
? [
|
|
2027
2128
|
{
|
|
@@ -2033,6 +2134,7 @@ function serverTab() {
|
|
|
2033
2134
|
]
|
|
2034
2135
|
: []),
|
|
2035
2136
|
])}
|
|
2137
|
+
${cacheSection()}
|
|
2036
2138
|
${upstreamSection()}
|
|
2037
2139
|
<h4>Recent requests</h4>
|
|
2038
2140
|
${
|
|
@@ -2052,6 +2154,42 @@ function serverTab() {
|
|
|
2052
2154
|
`;
|
|
2053
2155
|
}
|
|
2054
2156
|
|
|
2157
|
+
/**
|
|
2158
|
+
* HTML / veri önbelleğini panelden boşaltma. Admin panelindeki aynı işlemlerin
|
|
2159
|
+
* kısa yolu; "bu sayfa" mevcut `location.pathname` için sert düşürme yapar.
|
|
2160
|
+
*
|
|
2161
|
+
* @returns {string}
|
|
2162
|
+
*/
|
|
2163
|
+
function cacheSection() {
|
|
2164
|
+
const path = location.pathname || "/";
|
|
2165
|
+
const html = serverStats?.cache?.html ?? 0;
|
|
2166
|
+
const data = serverStats?.cache?.data ?? 0;
|
|
2167
|
+
|
|
2168
|
+
return `
|
|
2169
|
+
<h4>Cache</h4>
|
|
2170
|
+
${
|
|
2171
|
+
cacheNotice
|
|
2172
|
+
? `<div class="notice">${escapeHtml(cacheNotice)}</div>`
|
|
2173
|
+
: ""
|
|
2174
|
+
}
|
|
2175
|
+
<p class="hint block">
|
|
2176
|
+
In-process HTML (${html}) and data (${data}) entries.
|
|
2177
|
+
Clearing this page drops every variant of <code>${escapeHtml(path)}</code> and reloads.
|
|
2178
|
+
</p>
|
|
2179
|
+
<div class="toolbar start">
|
|
2180
|
+
<button class="btn mini" data-action="cache-page" title="Hard-drop HTML for ${escapeHtml(path)}">
|
|
2181
|
+
${TRASH_ICON} Clear this page
|
|
2182
|
+
</button>
|
|
2183
|
+
<button class="btn mini danger" data-action="cache-html" title="Clear every HTML cache entry">
|
|
2184
|
+
${TRASH_ICON} Clear all HTML
|
|
2185
|
+
</button>
|
|
2186
|
+
<button class="btn mini danger" data-action="cache-data" title="Clear every data-cache entry">
|
|
2187
|
+
${TRASH_ICON} Clear data cache
|
|
2188
|
+
</button>
|
|
2189
|
+
</div>
|
|
2190
|
+
`;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2055
2193
|
/**
|
|
2056
2194
|
* Upstream hız freninin durumu. Fren kapalıysa (varsayılan) hiçbir şey
|
|
2057
2195
|
* basılmaz; açıkken 429 fırtınasında hızın nereye indiğini görmek, ayarı
|
|
@@ -2224,7 +2362,7 @@ const ABOUT = {
|
|
|
2224
2362
|
<div class="step"><span class="n">1</span><span><b>Route</b><span><code>server/routes/</code> matches the URL and hands the request to the right controller.</span></span></div>
|
|
2225
2363
|
<div class="step"><span class="n">2</span><span><b>Controller</b><span><code>server/controllers/</code> gathers the data it needs from the <code>lib/</code> services and prepares the model for the template.</span></span></div>
|
|
2226
2364
|
<div class="step"><span class="n">3</span><span><b>Render</b><span><code>server/render.js</code> runs the EJS template and prints manifest entries and head hints into the layout.</span></span></div>
|
|
2227
|
-
<div class="step"><span class="n">4</span><span><b>HTML cache</b><span><code>server/html-cache.js</code> stores the output; if the same URL is requested again the template never runs. The
|
|
2365
|
+
<div class="step"><span class="n">4</span><span><b>HTML cache</b><span><code>server/html-cache.js</code> stores the output; if the same URL is requested again the template never runs. The Server tab shows entry counts and can clear this page, all HTML, or the data cache.</span></span></div>
|
|
2228
2366
|
<div class="step"><span class="n">5</span><span><b>Response</b><span>Compression and cache headers are decided in <code>server/middleware/</code>; responses like event streams skip compression.</span></span></div>
|
|
2229
2367
|
</div>
|
|
2230
2368
|
<p><strong>Prewarming:</strong> <code>server/prewarm.js</code> visits the most travelled
|
|
@@ -2734,6 +2872,21 @@ function bind(root) {
|
|
|
2734
2872
|
}
|
|
2735
2873
|
|
|
2736
2874
|
if (target.dataset.action === "clear") clearAll();
|
|
2875
|
+
|
|
2876
|
+
if (target.dataset.action === "cache-page") {
|
|
2877
|
+
void clearThisPageCache();
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
if (target.dataset.action === "cache-html") {
|
|
2882
|
+
void clearHtmlCacheAll();
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
if (target.dataset.action === "cache-data") {
|
|
2887
|
+
void clearDataCacheAll();
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2737
2890
|
});
|
|
2738
2891
|
}
|
|
2739
2892
|
|
package/src/config/defaults.js
CHANGED
|
@@ -51,12 +51,17 @@ export const DEFAULT_DEV_GATE_BYPASS = [
|
|
|
51
51
|
];
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* Klasik (liste tabanlı) prewarm ayarları. `onVisit`
|
|
55
|
-
* kullanılamaz — ya açılış/`prewarmPaths` turu, ya
|
|
56
|
-
* linkleri ısıtma
|
|
54
|
+
* Klasik (liste tabanlı) prewarm ayarları. `onVisit` ve `prewarm: false`
|
|
55
|
+
* (on-demand) ile birlikte kullanılamaz — ya açılış/`prewarmPaths` turu, ya
|
|
56
|
+
* ziyaret edilen sayfadaki linkleri ısıtma, ya yalnızca ziyaret yazımı.
|
|
57
|
+
*
|
|
58
|
+
* `mode` çözümleme sonucu: `"classic"` | `"onVisit"` | `"off"`.
|
|
59
|
+
* `prewarm: false` → `"off"` (erken TTL ısıtması dahil hiçbir arka plan yok).
|
|
57
60
|
*/
|
|
58
61
|
export const DEFAULT_PREWARM = {
|
|
59
62
|
enabled: true,
|
|
63
|
+
/** @type {"classic" | "onVisit" | "off"} */
|
|
64
|
+
mode: "classic",
|
|
60
65
|
max: 400,
|
|
61
66
|
intervalSeconds: 0,
|
|
62
67
|
/** Paralel ısıtma isteği; dev'de sunucu tek süreç olduğu için düşürülür. */
|
package/src/config/index.js
CHANGED
|
@@ -940,18 +940,38 @@ function normalizeOnVisit(raw) {
|
|
|
940
940
|
}
|
|
941
941
|
|
|
942
942
|
/**
|
|
943
|
-
* Klasik liste
|
|
944
|
-
*
|
|
943
|
+
* Klasik liste ısıtması, `onVisit` ve `prewarm: false` (on-demand) karşılıklı
|
|
944
|
+
* dışlayıcıdır. Yanlış moda sessizce düşülmesin diye yüklemede hata verir.
|
|
945
945
|
*
|
|
946
946
|
* @param {unknown} raw
|
|
947
947
|
* @returns {Record<string, unknown>}
|
|
948
948
|
*/
|
|
949
949
|
function normalizePrewarm(raw) {
|
|
950
|
+
// On-demand: yalnızca ziyaret yazımı. Klasik / onVisit / early-expiry yok.
|
|
951
|
+
if (raw === false) {
|
|
952
|
+
return {
|
|
953
|
+
...DEFAULT_PREWARM,
|
|
954
|
+
enabled: false,
|
|
955
|
+
mode: "off",
|
|
956
|
+
onVisit: { ...DEFAULT_PREWARM_ON_VISIT, enabled: false },
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (raw != null && (typeof raw !== "object" || Array.isArray(raw))) {
|
|
961
|
+
throw new Error(
|
|
962
|
+
"[config] cache().prewarm must be false, or an object " +
|
|
963
|
+
"(classic settings or { onVisit })",
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
950
967
|
const source =
|
|
951
968
|
raw && typeof raw === "object" && !Array.isArray(raw)
|
|
952
969
|
? /** @type {Record<string, unknown>} */ ({ ...raw })
|
|
953
970
|
: {};
|
|
954
971
|
|
|
972
|
+
// `mode` çözümleme alanı; kullanıcı yazarsa yok sayılır.
|
|
973
|
+
delete source.mode;
|
|
974
|
+
|
|
955
975
|
const onVisit = normalizeOnVisit(source.onVisit);
|
|
956
976
|
|
|
957
977
|
if (onVisit.enabled) {
|
|
@@ -977,12 +997,15 @@ function normalizePrewarm(raw) {
|
|
|
977
997
|
return {
|
|
978
998
|
...DEFAULT_PREWARM,
|
|
979
999
|
enabled: true,
|
|
1000
|
+
mode: "onVisit",
|
|
980
1001
|
onVisit,
|
|
981
1002
|
};
|
|
982
1003
|
}
|
|
983
1004
|
|
|
984
1005
|
// Klasik mod: `onVisit: false` yazılmış olabilir; diğer alanlar varsayılanlarla
|
|
985
1006
|
// birleşir. `onVisit` anahtarı çözülmüş nesnede her zaman durur.
|
|
1007
|
+
// `enabled: false` klasik turu keser ama early-expiry drain açık kalır —
|
|
1008
|
+
// tamamen kapatmak için `prewarm: false` (`mode: "off"`) kullanılır.
|
|
986
1009
|
const classic = { ...source };
|
|
987
1010
|
delete classic.onVisit;
|
|
988
1011
|
|
|
@@ -994,6 +1017,7 @@ function normalizePrewarm(raw) {
|
|
|
994
1017
|
return {
|
|
995
1018
|
...DEFAULT_PREWARM,
|
|
996
1019
|
...classic,
|
|
1020
|
+
mode: "classic",
|
|
997
1021
|
onVisit,
|
|
998
1022
|
};
|
|
999
1023
|
}
|
|
@@ -1698,6 +1722,17 @@ export async function loadConfig(options = {}) {
|
|
|
1698
1722
|
);
|
|
1699
1723
|
}
|
|
1700
1724
|
|
|
1725
|
+
if (
|
|
1726
|
+
config.prewarm?.mode === "off" &&
|
|
1727
|
+
typeof config.hooks?.prewarmPaths === "function"
|
|
1728
|
+
) {
|
|
1729
|
+
throw new Error(
|
|
1730
|
+
"[config] hooks.prewarmPaths() cannot be used with cache().prewarm: false. " +
|
|
1731
|
+
"On-demand mode only caches pages as they are visited; classic mode uses " +
|
|
1732
|
+
"prewarmPaths. Choose one.",
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1701
1736
|
// Dev'de build ve sunucu ayrı alt süreçler; üçü de aynı özeti basınca satır
|
|
1702
1737
|
// banner'ın ve build bloğunun arasına üç kez giriyor. Özeti dış süreç basar.
|
|
1703
1738
|
if (loaded && !process.env.JSKELET_CHILD) {
|
|
@@ -15,7 +15,12 @@ import * as log from "../../log.mjs";
|
|
|
15
15
|
import { FRAMEWORK_ROOT, getConfig } from "../../config/index.js";
|
|
16
16
|
import { getRequestContext } from "../../http/request-context.js";
|
|
17
17
|
import { prewarm, prewarmProgress } from "../prewarm.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
clearHtmlCache,
|
|
20
|
+
getHtmlCacheSize,
|
|
21
|
+
invalidateHtmlCache,
|
|
22
|
+
} from "../html-cache.js";
|
|
23
|
+
import { clearDataCache, getDataCacheSize } from "../data-cache.js";
|
|
19
24
|
import {
|
|
20
25
|
buildReport,
|
|
21
26
|
clearPageReports,
|
|
@@ -325,6 +330,12 @@ function statsPayload() {
|
|
|
325
330
|
version: versionStatus(),
|
|
326
331
|
memory: { rss: usage.rss, heapUsed: usage.heapUsed },
|
|
327
332
|
prewarm: { ...prewarmProgress },
|
|
333
|
+
// Overlay Server sekmesindeki önbellek sayaçları; her kalp atışında
|
|
334
|
+
// güncellenir ki temizleme sonrası yenilemeye gerek kalmasın.
|
|
335
|
+
cache: {
|
|
336
|
+
html: getHtmlCacheSize(),
|
|
337
|
+
data: getDataCacheSize(),
|
|
338
|
+
},
|
|
328
339
|
requests: requests.slice(-25).reverse(),
|
|
329
340
|
errors: errors.slice(-25).reverse(),
|
|
330
341
|
// SSR dış çağrıları: overlay API sekmesi hangi sayfanın hangi
|
|
@@ -557,6 +568,41 @@ function router() {
|
|
|
557
568
|
res.json({ ok: true });
|
|
558
569
|
});
|
|
559
570
|
|
|
571
|
+
// Panelden HTML / veri önbelleğini elle boşaltma. Admin panelindeki
|
|
572
|
+
// `html:clear` / `html:invalidate` / `data:clear` ile aynı çekirdek API;
|
|
573
|
+
// yalnızca development'ta ve CSRF'siz — overlay zaten aynı origin'den gelir.
|
|
574
|
+
api.post("/cache/clear-html", (req, res) => {
|
|
575
|
+
const cleared = getHtmlCacheSize();
|
|
576
|
+
clearHtmlCache();
|
|
577
|
+
pushStats();
|
|
578
|
+
res.json({ ok: true, cleared });
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
api.post("/cache/clear-data", express.json({ limit: "8kb" }), (req, res) => {
|
|
582
|
+
const prefix =
|
|
583
|
+
typeof req.body?.prefix === "string" && req.body.prefix
|
|
584
|
+
? req.body.prefix
|
|
585
|
+
: undefined;
|
|
586
|
+
const cleared = clearDataCache(prefix);
|
|
587
|
+
pushStats();
|
|
588
|
+
res.json({ ok: true, cleared });
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
api.post("/cache/invalidate", express.json({ limit: "8kb" }), (req, res) => {
|
|
592
|
+
const target = String(req.body?.path ?? "");
|
|
593
|
+
if (!target.startsWith("/")) {
|
|
594
|
+
res.status(400).json({ ok: false, reason: "path" });
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Dev panelinde "bu sayfayı sil" soğuk render bekler; yumuşak bayatlatma
|
|
599
|
+
// eski HTML'i servis etmeye devam eder ve etkisi görünmez kalır.
|
|
600
|
+
const hard = req.body?.hard !== false;
|
|
601
|
+
const count = invalidateHtmlCache(target, { hard });
|
|
602
|
+
pushStats();
|
|
603
|
+
res.json({ ok: true, count, path: target, hard });
|
|
604
|
+
});
|
|
605
|
+
|
|
560
606
|
return api;
|
|
561
607
|
}
|
|
562
608
|
|
package/src/server/prewarm.js
CHANGED
|
@@ -928,6 +928,8 @@ async function drainVisitWarm() {
|
|
|
928
928
|
* Açılışta ısıtmayı tetikler. `listen` geri çağrısından çağrılır.
|
|
929
929
|
* `onVisit` modunda klasik zamanlayıcı yok; yine de erken-TTL invalidation
|
|
930
930
|
* drain'i çalışır — soft-bayatlayan sweeper'ın kuyruğu boşalmasın.
|
|
931
|
+
* `prewarm: false` (`mode: "off"`) on-demand: ziyaret yazımı kalır, hiçbir
|
|
932
|
+
* arka plan ısıtması kurulmaz; `PREWARM=1` bunu ezmez.
|
|
931
933
|
*
|
|
932
934
|
* @param {{ port: number }} options
|
|
933
935
|
* @returns {void}
|
|
@@ -935,12 +937,14 @@ async function drainVisitWarm() {
|
|
|
935
937
|
export function startPrewarm({ port }) {
|
|
936
938
|
const config = getConfig();
|
|
937
939
|
if (process.env.PREWARM === "0") return;
|
|
940
|
+
// On-demand: L1/L2 ziyaretle dolar; classic / onVisit / early-expiry yok.
|
|
941
|
+
if (config.prewarm?.mode === "off") return;
|
|
938
942
|
|
|
939
943
|
const origins = resolvePrewarmOrigins(port);
|
|
940
944
|
const origin = origins[0];
|
|
941
945
|
|
|
942
946
|
// Klasik `prewarmPaths` olmasa da TTL öncesi soft-bayatlayan girdiler
|
|
943
|
-
// HTTP ile ısıtılsın. `PREWARM=0` yukarıda her şeyi keser.
|
|
947
|
+
// HTTP ile ısıtılsın. `PREWARM=0` / `mode: "off"` yukarıda her şeyi keser.
|
|
944
948
|
startEarlyExpirySweep();
|
|
945
949
|
startExpiryWarmDrain(origins);
|
|
946
950
|
|
|
@@ -40,12 +40,17 @@ export declare const DEFAULT_STATIC: {
|
|
|
40
40
|
/** Dev gate'in hiçbir koşulda kapatmadığı yollar. */
|
|
41
41
|
export declare const DEFAULT_DEV_GATE_BYPASS: string[];
|
|
42
42
|
/**
|
|
43
|
-
* Klasik (liste tabanlı) prewarm ayarları. `onVisit`
|
|
44
|
-
* kullanılamaz — ya açılış/`prewarmPaths` turu, ya
|
|
45
|
-
* linkleri ısıtma
|
|
43
|
+
* Klasik (liste tabanlı) prewarm ayarları. `onVisit` ve `prewarm: false`
|
|
44
|
+
* (on-demand) ile birlikte kullanılamaz — ya açılış/`prewarmPaths` turu, ya
|
|
45
|
+
* ziyaret edilen sayfadaki linkleri ısıtma, ya yalnızca ziyaret yazımı.
|
|
46
|
+
*
|
|
47
|
+
* `mode` çözümleme sonucu: `"classic"` | `"onVisit"` | `"off"`.
|
|
48
|
+
* `prewarm: false` → `"off"` (erken TTL ısıtması dahil hiçbir arka plan yok).
|
|
46
49
|
*/
|
|
47
50
|
export declare const DEFAULT_PREWARM: {
|
|
48
51
|
enabled: boolean;
|
|
52
|
+
/** @type {"classic" | "onVisit" | "off"} */
|
|
53
|
+
mode: "classic" | "onVisit" | "off";
|
|
49
54
|
max: number;
|
|
50
55
|
intervalSeconds: number;
|
|
51
56
|
/** Paralel ısıtma isteği; dev'de sunucu tek süreç olduğu için düşürülür. */
|
|
@@ -122,6 +122,8 @@ export declare function noteVisitWarm(html: string, context: {
|
|
|
122
122
|
* Açılışta ısıtmayı tetikler. `listen` geri çağrısından çağrılır.
|
|
123
123
|
* `onVisit` modunda klasik zamanlayıcı yok; yine de erken-TTL invalidation
|
|
124
124
|
* drain'i çalışır — soft-bayatlayan sweeper'ın kuyruğu boşalmasın.
|
|
125
|
+
* `prewarm: false` (`mode: "off"`) on-demand: ziyaret yazımı kalır, hiçbir
|
|
126
|
+
* arka plan ısıtması kurulmaz; `PREWARM=1` bunu ezmez.
|
|
125
127
|
*
|
|
126
128
|
* @param {{ port: number }} options
|
|
127
129
|
* @returns {void}
|