jskelet 0.6.16 → 0.6.17

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 CHANGED
@@ -17,6 +17,12 @@ one is listed under a **Breaking** heading.
17
17
 
18
18
  ### Added
19
19
 
20
+ - On-demand HTML cache via `cache().prewarm: false`: visited pages still
21
+ write to L1 and Redis/disk L2 with the usual `html` TTLs, but classic
22
+ prewarm, `onVisit` link warming, and early-expiry HTTP refresh stay off
23
+ (`PREWARM=1` does not override). Mutually exclusive with
24
+ `hooks.prewarmPaths`. Resolved `prewarm.mode` is `"off"` | `"classic"` |
25
+ `"onVisit"`.
20
26
  - When `build.assets.prefix` is set, the CSS build rewrites root-absolute
21
27
  `url()` paths under `static.prefixes` (e.g. `/fonts/…`) to the CDN
22
28
  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
- İki mod **karşılıklı dışlayıcıdır**. `cache().prewarm.onVisit` açıksa klasik
1177
- alanlar (`max`, `priority`, `rotate`, `intervalSeconds`, …) ve
1178
- `hooks.prewarmPaths` birlikte verilemez — config yüklenirken hata fırlar.
1179
- Tersi de geçerli: klasik liste ısıtması kullanıyorsanız `onVisit` yazmayın.
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.** Klasik modda `hooks.prewarmPaths` tanımlı değil,
1446
- `PREWARM=0` ayarlı ya da `cache().prewarm.enabled === false`. `onVisit`
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.** İki mod karşılıklı
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.
@@ -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
- İki mod: **klasik** (liste + açılış turu) veya **`onVisit`** (ziyaret edilen
1088
- sayfadaki linkler). Birlikte verilemez — config yüklenirken hata.
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 ısıtma yapılmaz (`PREWARM=1` ile ezilebilir) |
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` ile
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 birlikte **yasak** | [06](./06-cache.md) |
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 |
@@ -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
- The two modes are **mutually exclusive**. If `cache().prewarm.onVisit` is on,
1179
- classic fields (`max`, `priority`, `rotate`, `intervalSeconds`, …) and
1180
- `hooks.prewarmPaths` must not be set together — config load throws. The reverse
1181
- holds too: if you use classic list warming, do not set `onVisit`.
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.** In classic mode `hooks.prewarmPaths` is not
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`.** The two modes are
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
- Two modes: **classic** (list + startup pass) or **`onVisit`** (links from the
1110
- page just visited). They cannot be combined — config load throws.
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 prewarming happens (can be overridden with `PREWARM=1`) |
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jskelet",
3
- "version": "0.6.16",
3
+ "version": "0.6.17",
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",
@@ -51,12 +51,17 @@ export const DEFAULT_DEV_GATE_BYPASS = [
51
51
  ];
52
52
 
53
53
  /**
54
- * Klasik (liste tabanlı) prewarm ayarları. `onVisit` modu bunlarla birlikte
55
- * kullanılamaz — ya açılış/`prewarmPaths` turu, ya ziyaret edilen sayfadaki
56
- * linkleri ısıtma.
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. */
@@ -940,18 +940,38 @@ function normalizeOnVisit(raw) {
940
940
  }
941
941
 
942
942
  /**
943
- * Klasik liste ısıtması ile `onVisit` karşılıklı dışlayıcıdır. İkisini birden
944
- * yazmak sessizce yanlış moda düşmesin diye yüklemede hata verir.
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) {
@@ -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` modu bunlarla birlikte
44
- * kullanılamaz — ya açılış/`prewarmPaths` turu, ya ziyaret edilen sayfadaki
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}