jskelet 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -3
- package/CHANGELOG.md +158 -103
- package/README.md +7 -14
- package/bin/jskelet.mjs +1 -1
- package/docs/02-mimari.md +6 -1
- package/docs/03-routing.md +3 -1
- package/docs/04-render-ve-sablonlar.md +25 -0
- package/docs/06-cache.md +18 -7
- package/docs/07-yapilandirma.md +29 -9
- package/docs/09-dev-araclari.md +17 -7
- package/docs/10-dagitim.md +8 -12
- package/docs/README.md +3 -28
- package/docs/en/02-architecture.md +7 -1
- package/docs/en/03-routing.md +3 -1
- package/docs/en/04-rendering.md +24 -0
- package/docs/en/06-caching.md +21 -7
- package/docs/en/07-configuration.md +29 -9
- package/docs/en/09-dev-tools.md +18 -7
- package/docs/en/10-deployment.md +8 -13
- package/docs/en/README.md +3 -30
- package/package.json +2 -2
- package/src/config/defaults.js +36 -3
- package/src/config/index.js +137 -26
- package/src/server/create-app.js +9 -0
- package/src/server/html-cache.js +178 -32
- package/src/server/middleware/dev-gate.js +21 -8
- package/src/server/middleware/robots-txt.js +341 -0
- package/src/server/prewarm.js +137 -51
- package/src/server/render.js +3 -1
- package/types/config/defaults.d.ts +31 -3
- package/types/config/index.d.ts +5 -0
- package/types/server/html-cache.d.ts +40 -6
- package/types/server/middleware/robots-txt.d.ts +33 -0
- package/types/server/prewarm.d.ts +6 -3
package/docs/10-dagitim.md
CHANGED
|
@@ -50,7 +50,7 @@ ayarlamayı düşünmeniz gerekenler:
|
|
|
50
50
|
| `HOST` | `0.0.0.0` | Yalnızca IPv4 dinlemek gerekiyorsa. Varsayılan `::` zaten çift yığın dinler |
|
|
51
51
|
| `PREWARM_MAX` | Site boyutuna göre | Açılışta ısıtılacak sayfa sayısı |
|
|
52
52
|
| `PREWARM_INTERVAL_SECONDS` | `0` ya da uzun bir değer | Hiç ziyaret edilmeyen sayfaları sıcak tutmak isterseniz |
|
|
53
|
-
| `DEV_TOKEN` | Yalnızca staging'de | Yayına açılmamış ortamı gizler |
|
|
53
|
+
| `DEV_GATE` + `DEV_TOKEN` | Yalnızca staging'de | Yayına açılmamış ortamı gizler. Token tek başına siteyi kilitlemez |
|
|
54
54
|
| `JSKELET_S3_*` | Access log'u S3'e yazıyorsanız | Bucket + credential; ayrıntı [07](./07-yapilandirma.md) |
|
|
55
55
|
|
|
56
56
|
Production'da dosya veya S3 sink açıldığında HTTP access log middleware
|
|
@@ -156,15 +156,11 @@ Build aşaması `npx jskelet build` ile bunları kendisi üretir.
|
|
|
156
156
|
|
|
157
157
|
Bu depodaki örnekler jskelet'i npm'den değil `"jskelet": "file:../.."` ile
|
|
158
158
|
alıyor. Coolify, Railway, Render gibi araçlarda "base directory" olarak
|
|
159
|
-
`examples/
|
|
159
|
+
`examples/blog` verilirse build context yalnızca o dizin olur, `../..`
|
|
160
160
|
context'in dışında kalır ve kurulum `npm ci`de düşer. Doğru ayar: **base
|
|
161
|
-
directory
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
docker build -f examples/marketing/Dockerfile -t jskelet-marketing .
|
|
166
|
-
docker run --rm -p 3000:3000 -e SITE_URL=https://example.com jskelet-marketing
|
|
167
|
-
```
|
|
161
|
+
directory `/`** (depo kökü) ve imajı yukarıdaki çok aşamalı Dockerfile ile
|
|
162
|
+
uygulama dizinine göre uyarlamak — ya da jskelet'i npm bağımlılığı olarak
|
|
163
|
+
kurup context'i uygulamanın kendi dizini yapmak.
|
|
168
164
|
|
|
169
165
|
Kendi uygulamanızda jskelet normal bir bağımlılık olacağı için bu kısıt yoktur;
|
|
170
166
|
yukarıdaki çok aşamalı imaj yeterli.
|
|
@@ -173,8 +169,8 @@ yukarıdaki çok aşamalı imaj yeterli.
|
|
|
173
169
|
|
|
174
170
|
Framework hazır bir sağlık kontrolü ucu **eklemez**; kendi route'unuza koymanız
|
|
175
171
|
gerekir. Varsayılan `devGateBypass` listesi `/api/healthcheck` yolunu içerdiği
|
|
176
|
-
için bu adı kullanmak en az sürprizli seçenektir:
|
|
177
|
-
|
|
172
|
+
için bu adı kullanmak en az sürprizli seçenektir: dev gate açıkken bile
|
|
173
|
+
erişilebilir kalır.
|
|
178
174
|
|
|
179
175
|
```js
|
|
180
176
|
// routes/00-health.mjs
|
|
@@ -331,7 +327,7 @@ istek başına iş neredeyse sıfıra iner.
|
|
|
331
327
|
- [ ] `hooks.prewarmPaths()` en önemli sayfaları başa koyuyor
|
|
332
328
|
- [ ] `headers()` içinde CSP ve güvenlik başlıkları tanımlı
|
|
333
329
|
- [ ] Sağlık kontrolü ucu var ve `devGateBypass` listesinde
|
|
334
|
-
- [ ] Staging'de `DEV_TOKEN` ayarlı, prod'da
|
|
330
|
+
- [ ] Staging'de `DEV_GATE=1` ve `DEV_TOKEN` ayarlı, prod'da gate **kapalı**
|
|
335
331
|
- [ ] Ters proxy `Accept-Encoding`i iletiyor ve kendi sıkıştırmasını yapmıyor
|
|
336
332
|
- [ ] `clientEnv` listesinde gizli anahtar yok
|
|
337
333
|
|
package/docs/README.md
CHANGED
|
@@ -47,7 +47,7 @@ eşlenik tutuluyor; birini değiştiriyorsan diğerini de değiştir.
|
|
|
47
47
|
|
|
48
48
|
## Çalışan örnekler
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Üçü de çalışır durumda; belgelerdeki örneklerin çoğu buralardan alınmıştır.
|
|
51
51
|
|
|
52
52
|
**`examples/minimal/`** — iki route, bir bileşen, bir island, minimal config.
|
|
53
53
|
Framework'ün en küçük çalışan hâli.
|
|
@@ -68,32 +68,7 @@ npm --prefix examples/blog install
|
|
|
68
68
|
npm --prefix examples/blog run dev
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
**`examples/
|
|
72
|
-
tablosu, canlı gecikme ölçümü, SSS, belgeler dizini, sürüm notları ve indirme
|
|
73
|
-
sayfası. Sayfadaki bayt sayıları `lib/payload.js` içinde sitenin **kendi** build
|
|
74
|
-
çıktısından, sürüm künyesi ise `lib/release.js` içinde kurulu paketin
|
|
75
|
-
`package.json`'ından okunur; gecikme sayıları `latency` island'ında tarayıcıda
|
|
76
|
-
ölçülür. Uzun TTL (bir saat) ve tüm sayfaları ısıtan prewarm ile, cache'in en
|
|
77
|
-
verimli çalıştığı profili gösterir.
|
|
78
|
-
|
|
79
|
-
Site aynı zamanda **bu belgeleri** servis ediyor: `/docs/<bölüm>` adresleri
|
|
80
|
-
`node_modules/jskelet/docs/` altındaki markdown dosyalarını okuyup sol gezinme,
|
|
81
|
-
"bu sayfada" listesi ve sıralı geçişle basıyor. Çevirici `lib/markdown.js`
|
|
82
|
-
içinde küçük bir modül — bağımlılık yok — ve kaynak paketin kendisi olduğu için
|
|
83
|
-
site kurulu sürümden hiç ayrışmıyor.
|
|
84
|
-
|
|
85
|
-
Site aynı zamanda **iki dilli**: varsayılan İngilizce kökte, Türkçe `/tr`
|
|
86
|
-
altında ve route adları iki dilde de aynı. Framework'te i18n yok; dil
|
|
87
|
-
çözümlemesi `lib/i18n.js` içinde uygulamanın kendi sözleşmesi olarak duruyor ve
|
|
88
|
-
`hooks.layoutContext` ile bir sözlüğe bağlanıyor. Çok dilli bir siteyi bu
|
|
89
|
-
yüzeyle nasıl kurabileceğinizi görmek için bakılacak yer burası.
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
npm --prefix examples/marketing install
|
|
93
|
-
npm --prefix examples/marketing run dev
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
**`examples/dashboard/`** — diğer üçünün tersi eksen: kişiye özel sayfalar.
|
|
71
|
+
**`examples/dashboard/`** — diğer ikisinin tersi eksen: kişiye özel sayfalar.
|
|
97
72
|
İmzalı cookie ile giriş, `private: true` korumalı panel, sayfalı tablo
|
|
98
73
|
fragment'i, CSRF'li mutasyon formu ve temizlik fonksiyonu döndüren bir island.
|
|
99
74
|
Public bir tanıtım sayfası da var, böylece aynı uygulamada önbelleklenen ve
|
|
@@ -104,5 +79,5 @@ npm --prefix examples/dashboard install
|
|
|
104
79
|
npm --prefix examples/dashboard run dev
|
|
105
80
|
```
|
|
106
81
|
|
|
107
|
-
Her
|
|
82
|
+
Her üç örnekte `node smoke.mjs` sunucu ayaktayken uçların beklendiği gibi
|
|
108
83
|
yanıt verdiğini doğrular.
|
|
@@ -36,9 +36,10 @@ Request
|
|
|
36
36
|
├─ rewrites(beforeFiles) config → proxy or a change to req.url
|
|
37
37
|
├─ compression brotli/gzip negotiation (quality 5)
|
|
38
38
|
├─ headers static cache + config headers()
|
|
39
|
-
├─ devGate if
|
|
39
|
+
├─ devGate if the gate is on, 404 without a token
|
|
40
40
|
├─ redirects config redirects(), first match wins
|
|
41
41
|
├─ trailingSlash 308 when config trailingSlash is true
|
|
42
|
+
├─ robots.txt appends framework Disallow rules to the user's body
|
|
42
43
|
├─ staticPrecompressed .br/.gz copies produced at build (quality 11)
|
|
43
44
|
├─ express.static files under public/
|
|
44
45
|
├─ (dev) devtools only when NODE_ENV=development
|
|
@@ -71,6 +72,11 @@ position has a reason, and moving things around leads to silent breakage.
|
|
|
71
72
|
leak even its redirect rules to the outside. `trailingSlash` sits after config
|
|
72
73
|
redirects so explicit rules see the requested path first; the canonical slash
|
|
73
74
|
form is enforced as a second step.
|
|
75
|
+
- **`robots.txt` before static, and inside compression.** The body the
|
|
76
|
+
application wrote is left intact; the framework appends `Disallow` rules
|
|
77
|
+
for its own endpoints. A response that already has `Content-Encoding` is
|
|
78
|
+
not rewritten — the block is added to plain text, and compression stays
|
|
79
|
+
outside.
|
|
74
80
|
- **`staticPrecompressed` before `express.static`.** If there are `.br`/`.gz`
|
|
75
81
|
copies produced at build time, those are served (brotli quality 11);
|
|
76
82
|
otherwise the request falls through to the `static` below it and the
|
package/docs/en/03-routing.md
CHANGED
|
@@ -158,7 +158,9 @@ stored), but the flag is the right place. Details in
|
|
|
158
158
|
## `fragment()` — a partial without the layout
|
|
159
159
|
|
|
160
160
|
For endpoints that refresh a region. No layout is printed, the response is sent
|
|
161
|
-
with `private, no-store` and no ETag, and it never touches the HTML cache.
|
|
161
|
+
with `private, no-store` and no ETag, and it never touches the HTML cache. The
|
|
162
|
+
`/_fragment/` prefix is appended to `robots.txt`, so partial endpoints are not
|
|
163
|
+
indexed ([04](./04-rendering.md#robotstxt)).
|
|
162
164
|
|
|
163
165
|
```js
|
|
164
166
|
app.get(
|
package/docs/en/04-rendering.md
CHANGED
|
@@ -510,6 +510,30 @@ The `renderHeadMeta(metadata)` function is exported; it can be used when you
|
|
|
510
510
|
need to produce the same tags outside the layout (for example in a fragment or
|
|
511
511
|
an email).
|
|
512
512
|
|
|
513
|
+
## robots.txt
|
|
514
|
+
|
|
515
|
+
The application writes `robots.txt`: `public/robots.txt` or a plain route.
|
|
516
|
+
The framework does not change that body; it appends a JSkelet note and
|
|
517
|
+
`Disallow` rules **under** a successful text response. If there is no file
|
|
518
|
+
and no route, the framework does not invent a `robots.txt`.
|
|
519
|
+
|
|
520
|
+
Paths added:
|
|
521
|
+
|
|
522
|
+
- `/_jskelet/` — admin panel, remote image proxy, auth handoff
|
|
523
|
+
- `/__jskelet/` — development tools
|
|
524
|
+
- `/_fragment/` — partial responses without a layout
|
|
525
|
+
|
|
526
|
+
An endpoint moved off those prefixes is added too, but only when it is
|
|
527
|
+
actually mounted: `admin.basePath`, `images.remote.path`,
|
|
528
|
+
`auth.crossSubdomainHandoff.path`. `brand.devBasePath` is written only in
|
|
529
|
+
development; in production that path may be the application's own page.
|
|
530
|
+
|
|
531
|
+
The note starts with the configured brand name (`brand.name`, default
|
|
532
|
+
`JSkelet`). The trailing group repeats `User-agent: *` together with every
|
|
533
|
+
other agent already named in the file. Google does not merge a
|
|
534
|
+
crawler-specific group with `*`; it does merge a second group for the same
|
|
535
|
+
agent. If the note is already in the file, it is not appended again.
|
|
536
|
+
|
|
513
537
|
## Dynamic OG images
|
|
514
538
|
|
|
515
539
|
Counterpart to Next.js `ImageResponse` / `opengraph-image.tsx`. There is no JSX:
|
package/docs/en/06-caching.md
CHANGED
|
@@ -231,7 +231,12 @@ price is acceptable, because live fields such as prices are updated on the
|
|
|
231
231
|
client over WebSocket.
|
|
232
232
|
|
|
233
233
|
The store is an LRU: an accessed entry is moved to the end, and once the limit
|
|
234
|
-
(`cache().maxEntries`, 500 by default) is exceeded the oldest is evicted.
|
|
234
|
+
(`cache().maxEntries`, 500 by default) is exceeded the oldest is evicted. Config
|
|
235
|
+
may ask for more than 500; it **cannot exceed 800** — a higher value is clamped
|
|
236
|
+
to 800 with a warning. Separately, in-process HTML strings plus compressed
|
|
237
|
+
bodies **cannot exceed 256 MB**. A fat page or a `vary.host` copy that is still
|
|
238
|
+
under the count limit is evicted by this budget too. A single page larger than
|
|
239
|
+
256 MB is not stored; that response is still sent.
|
|
235
240
|
|
|
236
241
|
## What gets written to the cache
|
|
237
242
|
|
|
@@ -1145,9 +1150,9 @@ export default {
|
|
|
1145
1150
|
html: { "/": 60, "/news/:slug": 300 },
|
|
1146
1151
|
prewarm: {
|
|
1147
1152
|
onVisit: {
|
|
1148
|
-
perPage: 20, // at most this many links per page
|
|
1149
|
-
concurrency: 2, //
|
|
1150
|
-
rps:
|
|
1153
|
+
perPage: 20, // at most this many links per page; ceiling 20
|
|
1154
|
+
concurrency: 2, // ceiling 2
|
|
1155
|
+
rps: 2, // ceiling 2; 0 is clamped to 2 as well
|
|
1151
1156
|
},
|
|
1152
1157
|
},
|
|
1153
1158
|
};
|
|
@@ -1163,7 +1168,15 @@ Rules:
|
|
|
1163
1168
|
degraded or `no-store` responses do not extract links.
|
|
1164
1169
|
- The warmer's own UA (`brand.prewarmUserAgent`) does not trigger — no crawl
|
|
1165
1170
|
loop.
|
|
1166
|
-
- Paths that are already fresh are not enqueued.
|
|
1171
|
+
- Paths that are already fresh are not enqueued. Real keys look like
|
|
1172
|
+
`h=host|/path?`; the check sees the vary prefix and the trailing `?`.
|
|
1173
|
+
With `vary.host`, only this request's host counts as fresh.
|
|
1174
|
+
- The pending queue holds at most 64 paths; links beyond that are left for a
|
|
1175
|
+
later response.
|
|
1176
|
+
- `perPage` 20, `rps` 2 and `concurrency` 2 are ceilings. A higher value (and
|
|
1177
|
+
`rps: 0`) is clamped with a warning. Warm requests stay on loopback; when
|
|
1178
|
+
`vary.host` is on, the public host is sent as `x-forwarded-host`, so a second
|
|
1179
|
+
`h=127.0.0.1` entry is not created.
|
|
1167
1180
|
- `nofollow`, `target="_blank"`, `data-no-prefetch`, `prewarmSkip` and
|
|
1168
1181
|
`navigation.exclude` share the same exemptions as Speculation Rules.
|
|
1169
1182
|
- Query strings are not warmed (default cache policy treats query as dynamic).
|
|
@@ -1308,8 +1321,9 @@ The requests go out with the headers `user-agent: jskelet-prewarm`
|
|
|
1308
1321
|
(`brand.prewarmUserAgent`) and `accept-encoding: br, gzip`; the second one so
|
|
1309
1322
|
that the compressed body enters the cache too.
|
|
1310
1323
|
|
|
1311
|
-
If
|
|
1312
|
-
|
|
1324
|
+
If the dev gate is on, the warm-up carries the token as a cookie; otherwise the
|
|
1325
|
+
gate returns 404 for all pages and the cache never fills. `DEV_TOKEN` alone
|
|
1326
|
+
does not turn the gate on.
|
|
1313
1327
|
|
|
1314
1328
|
The request list in the dev panel and the terminal filter out requests carrying
|
|
1315
1329
|
`prewarmUserAgent`: so that hundreds of warm-up requests do not flood the view.
|
|
@@ -298,6 +298,21 @@ static: {
|
|
|
298
298
|
}
|
|
299
299
|
```
|
|
300
300
|
|
|
301
|
+
## `devGate`
|
|
302
|
+
|
|
303
|
+
**Type:** `boolean` — **Default:** `false`
|
|
304
|
+
|
|
305
|
+
Hides an environment that is not public yet. **`DEV_TOKEN` alone does not lock
|
|
306
|
+
the site.** A shared task definition can carry the same variable into
|
|
307
|
+
production; visitors are not required to present a token, and the site stays
|
|
308
|
+
open.
|
|
309
|
+
|
|
310
|
+
Turn the gate on with `devGate: true` or `DEV_GATE=1`. Then a request without
|
|
311
|
+
the token gets a 404. `DEV_GATE=0` also turns off a gate the config enabled.
|
|
312
|
+
If the token is empty, requests still pass even when the gate is on.
|
|
313
|
+
|
|
314
|
+
Details: [09-dev-tools.md](./09-dev-tools.md).
|
|
315
|
+
|
|
301
316
|
## `devGateBypass`
|
|
302
317
|
|
|
303
318
|
**Type:** `string[]` — **Default:**
|
|
@@ -305,8 +320,7 @@ static: {
|
|
|
305
320
|
|
|
306
321
|
**Exact** paths the dev gate never closes off under any circumstances (not a
|
|
307
322
|
prefix, an exact match). This is so that the health check and the robots files
|
|
308
|
-
stay reachable
|
|
309
|
-
replaces the default.
|
|
323
|
+
stay reachable while the gate is on. If provided, it replaces the default.
|
|
310
324
|
|
|
311
325
|
Details: [09-dev-tools.md](./09-dev-tools.md).
|
|
312
326
|
|
|
@@ -441,7 +455,8 @@ body > footer { view-transition-name: site-footer; }
|
|
|
441
455
|
::view-transition-new(root) { animation-duration: 180ms; }
|
|
442
456
|
```
|
|
443
457
|
|
|
444
|
-
|
|
458
|
+
Copy the Tailwind `@source` directives and view-transition CSS into your own
|
|
459
|
+
app's `styles/globals.css`; the blocks above are a starting point.
|
|
445
460
|
|
|
446
461
|
**If you use CSP**, the rules are emitted as an inline
|
|
447
462
|
`<script type="speculationrules">`; your `script-src` policy needs to allow it.
|
|
@@ -771,6 +786,10 @@ raising this number burns through memory quickly; trying to solve a site with
|
|
|
771
786
|
tens of thousands of paths from here is the wrong layer — the right place is
|
|
772
787
|
`cache().data`.
|
|
773
788
|
|
|
789
|
+
**Ceiling 800.** A higher value is clamped to 800 with a warning at load.
|
|
790
|
+
In-process HTML plus compressed bodies also cannot exceed 256 MB; config
|
|
791
|
+
cannot raise that budget.
|
|
792
|
+
|
|
774
793
|
### `cache().data`
|
|
775
794
|
|
|
776
795
|
The upstream data cache (`withDataCache`). Details:
|
|
@@ -778,7 +797,7 @@ The upstream data cache (`withDataCache`). Details:
|
|
|
778
797
|
|
|
779
798
|
| Field | Type | Default | Meaning |
|
|
780
799
|
| --- | --- | --- | --- |
|
|
781
|
-
| `maxEntries` | `number` | `10000` | The LRU entry limit. The limit is high because JSON is tens of times smaller than HTML. |
|
|
800
|
+
| `maxEntries` | `number` | `10000` | The LRU entry limit. The limit is high because JSON is tens of times smaller than HTML. **Ceiling 20,000**; a higher value is clamped with a warning. |
|
|
782
801
|
| `staleFactor` | `number` | `10` | For how many TTLs an entry stays usable after the TTL expired. `0` → no stale serving. |
|
|
783
802
|
|
|
784
803
|
### `cache().trackUpstream`
|
|
@@ -1023,13 +1042,13 @@ prewarm: {
|
|
|
1023
1042
|
| Field | Type | Default | Meaning |
|
|
1024
1043
|
| --- | --- | --- | --- |
|
|
1025
1044
|
| `onVisit` | `true \| false \| object` | off | Visit-driven warming |
|
|
1026
|
-
| `onVisit.perPage` | `number` | `20` | At most how many links per page (top to bottom) |
|
|
1027
|
-
| `onVisit.concurrency` | `number` |
|
|
1028
|
-
| `onVisit.rps` | `number` |
|
|
1045
|
+
| `onVisit.perPage` | `number` | `20` | At most how many links per page (top to bottom). **Ceiling 20** |
|
|
1046
|
+
| `onVisit.concurrency` | `number` | `2` | Parallel workers. **Ceiling 2** |
|
|
1047
|
+
| `onVisit.rps` | `number` | `2` | Requests per second cap. **Ceiling 2**; `0` is clamped to 2 as well |
|
|
1029
1048
|
|
|
1030
1049
|
```js
|
|
1031
1050
|
prewarm: {
|
|
1032
|
-
onVisit: { perPage: 20, rps:
|
|
1051
|
+
onVisit: { perPage: 20, rps: 2 },
|
|
1033
1052
|
}
|
|
1034
1053
|
```
|
|
1035
1054
|
|
|
@@ -1129,7 +1148,8 @@ and no warning is printed.
|
|
|
1129
1148
|
| `PORT` | `startServer` | `3000` | Port to listen on. If busy, the process refuses to start; `jskelet start|dev --murder` kills the listener |
|
|
1130
1149
|
| `HOST` | `startServer` | `::` | Interface to bind to. The default listens dual-stack (IPv6 + IPv4); it falls back to `0.0.0.0` where IPv6 is unavailable |
|
|
1131
1150
|
| `JSKELET_SECRET` | `jskelet/cookies` | — | The signed cookie secret. Read when `security.cookieSecret` is not set; if neither exists, the signed cookie API throws. [12](./12-dashboards-and-sessions.md) |
|
|
1132
|
-
| `
|
|
1151
|
+
| `DEV_GATE` | `devGate` | off | `1` turns the gate on, `0` turns it off even when config enabled it. `DEV_TOKEN` alone does not turn it on. [09](./09-dev-tools.md) |
|
|
1152
|
+
| `DEV_TOKEN` | `devGate`, `prewarm` | — | The secret expected while the gate is on. If it is missing, or the gate is off, the site stays public. Prewarming carries the token as a cookie only while the gate is on. [09](./09-dev-tools.md) |
|
|
1133
1153
|
| `JSKELET_ADMIN` | `createApp` | — | When set, turns the admin panel on; `0` turns off a panel enabled in the config. The env wins because the panel is usually opened once during an incident. [06](./06-caching.md) |
|
|
1134
1154
|
| `JSKELET_LOG_BUCKET` | `logs.s3` | — | Log target: bucket or `bucket/prefix` path. With credentials, the sink turns on automatically |
|
|
1135
1155
|
| `JSKELET_S3_BUCKET` | `logs.s3` | — | Bucket when `JSKELET_LOG_BUCKET` is unset; joins with `JSKELET_S3_KEY_PREFIX` |
|
package/docs/en/09-dev-tools.md
CHANGED
|
@@ -303,11 +303,20 @@ production process.
|
|
|
303
303
|
|
|
304
304
|
## Dev gate — `DEV_TOKEN`
|
|
305
305
|
|
|
306
|
-
To hide an environment that is not public yet
|
|
307
|
-
|
|
306
|
+
To hide an environment that is not public yet. **The framework does not require
|
|
307
|
+
the token:** a `DEV_TOKEN` sitting in the environment does not lock the site.
|
|
308
|
+
You turn the gate on.
|
|
308
309
|
|
|
309
310
|
```bash
|
|
310
|
-
DEV_TOKEN=a-long-random-string npm start
|
|
311
|
+
DEV_GATE=1 DEV_TOKEN=a-long-random-string npm start
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The same thing from config:
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
export default {
|
|
318
|
+
devGate: true,
|
|
319
|
+
};
|
|
311
320
|
```
|
|
312
321
|
|
|
313
322
|
Access:
|
|
@@ -328,10 +337,12 @@ Behavior:
|
|
|
328
337
|
`/site.webmanifest`, `/favicon.ico`. If your health check lives at a different
|
|
329
338
|
path, remember to add it to this list, otherwise your orchestrator will see a
|
|
330
339
|
404.
|
|
331
|
-
-
|
|
332
|
-
production
|
|
333
|
-
|
|
334
|
-
|
|
340
|
+
- While `devGate` is off (the default) or `DEV_TOKEN` is empty, the middleware
|
|
341
|
+
passes the request through. A `DEV_TOKEN` that leaked into a production task
|
|
342
|
+
does not ask visitors for a token; startup prints a warning.
|
|
343
|
+
- `DEV_GATE=0` turns the gate off even when config says `devGate: true`.
|
|
344
|
+
- While the gate is on, warming carries the token as a cookie; without it every
|
|
345
|
+
page gets a 404 and the cache never fills up
|
|
335
346
|
([06-caching.md](./06-caching.md)).
|
|
336
347
|
|
|
337
348
|
In the middleware chain the gate sits after `headers` and **before**
|
package/docs/en/10-deployment.md
CHANGED
|
@@ -51,7 +51,7 @@ considering in production:
|
|
|
51
51
|
| `HOST` | `0.0.0.0` | Only if you need to listen on IPv4 alone; the `::` default already listens dual-stack |
|
|
52
52
|
| `PREWARM_MAX` | Depends on site size | Number of pages warmed at startup |
|
|
53
53
|
| `PREWARM_INTERVAL_SECONDS` | `0` or a long value | If you want to keep never-visited pages warm |
|
|
54
|
-
| `DEV_TOKEN` | Staging only | Hides an environment that is not public yet |
|
|
54
|
+
| `DEV_GATE` + `DEV_TOKEN` | Staging only | Hides an environment that is not public yet. The token alone does not lock the site |
|
|
55
55
|
| `JSKELET_S3_*` | If you write access logs to S3 | Bucket + credentials; details in [07](./07-configuration.md) |
|
|
56
56
|
|
|
57
57
|
When a file or S3 sink is enabled in production, the HTTP access log middleware
|
|
@@ -157,16 +157,11 @@ The build stage produces these itself with `npx jskelet build`.
|
|
|
157
157
|
|
|
158
158
|
The examples in this repo pull jskelet with `"jskelet": "file:../.."` rather
|
|
159
159
|
than from npm. In tools like Coolify, Railway or Render, if you set the "base
|
|
160
|
-
directory" to `examples/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
docker build -f examples/marketing/Dockerfile -t jskelet-marketing .
|
|
168
|
-
docker run --rm -p 3000:3000 -e SITE_URL=https://example.com jskelet-marketing
|
|
169
|
-
```
|
|
160
|
+
directory" to `examples/blog`, the build context becomes only that directory,
|
|
161
|
+
`../..` falls outside the context, and installation fails at `npm ci`. The
|
|
162
|
+
correct setting: **base directory `/`** (the repo root) and adapt the
|
|
163
|
+
multi-stage Dockerfile above to the app directory — or install jskelet as a
|
|
164
|
+
normal npm dependency and use the app directory as the context.
|
|
170
165
|
|
|
171
166
|
In your own application jskelet will be an ordinary dependency, so this
|
|
172
167
|
constraint does not apply; the multi-stage image above is enough.
|
|
@@ -176,7 +171,7 @@ constraint does not apply; the multi-stage image above is enough.
|
|
|
176
171
|
The framework does **not** add a ready-made health check endpoint; you have to
|
|
177
172
|
put it in your own route. Since the default `devGateBypass` list contains
|
|
178
173
|
`/api/healthcheck`, using that name is the least surprising option: it stays
|
|
179
|
-
reachable even
|
|
174
|
+
reachable even while the dev gate is on.
|
|
180
175
|
|
|
181
176
|
```js
|
|
182
177
|
// routes/00-health.mjs
|
|
@@ -333,7 +328,7 @@ goes up, the work per request drops to almost zero.
|
|
|
333
328
|
- [ ] `hooks.prewarmPaths()` puts the most important pages first
|
|
334
329
|
- [ ] CSP and security headers are defined in `headers()`
|
|
335
330
|
- [ ] A health check endpoint exists and is in the `devGateBypass` list
|
|
336
|
-
- [ ] `
|
|
331
|
+
- [ ] Staging has `DEV_GATE=1` and `DEV_TOKEN`; production leaves the gate **off**
|
|
337
332
|
- [ ] The reverse proxy forwards `Accept-Encoding` and does not do its own
|
|
338
333
|
compression
|
|
339
334
|
- [ ] There are no secret keys in the `clientEnv` list
|
package/docs/en/README.md
CHANGED
|
@@ -50,7 +50,7 @@ change one, change the other.
|
|
|
50
50
|
|
|
51
51
|
## Runnable examples
|
|
52
52
|
|
|
53
|
-
All
|
|
53
|
+
All three are in working order; most of the examples in the docs were taken from
|
|
54
54
|
them.
|
|
55
55
|
|
|
56
56
|
**`examples/minimal/`** — two routes, one component, one island, minimal config.
|
|
@@ -72,34 +72,7 @@ npm --prefix examples/blog install
|
|
|
72
72
|
npm --prefix examples/blog run dev
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
**`examples/
|
|
76
|
-
table, live latency measurement, FAQ, docs index, release notes and a download
|
|
77
|
-
page. The byte counts on the page are read in `lib/payload.js` from the site's
|
|
78
|
-
**own** build output, and the release info in `lib/release.js` from the
|
|
79
|
-
installed package's `package.json`; the latency numbers are measured in the
|
|
80
|
-
browser by the `latency` island. With a long TTL (one hour) and a prewarm that
|
|
81
|
-
warms every page, it shows the profile in which the cache works most
|
|
82
|
-
efficiently.
|
|
83
|
-
|
|
84
|
-
It also serves **these documents**: `/docs/<chapter>` reads the markdown files
|
|
85
|
-
in `node_modules/jskelet/docs/` and renders them with a sidebar, an "on this
|
|
86
|
-
page" list and sequential navigation. The renderer is a small module in
|
|
87
|
-
`lib/markdown.js` — no dependency — and the source of truth stays the package,
|
|
88
|
-
so the site never drifts from the installed version.
|
|
89
|
-
|
|
90
|
-
The site is also **bilingual**: English by default at the root, Turkish under
|
|
91
|
-
`/tr`, with the same route names in both languages. There is no i18n in the
|
|
92
|
-
framework; language resolution lives in `lib/i18n.js` as the application's own
|
|
93
|
-
contract and is wired to a dictionary via `hooks.layoutContext`. This is the
|
|
94
|
-
place to look if you want to see how to build a multilingual site with this
|
|
95
|
-
surface.
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
npm --prefix examples/marketing install
|
|
99
|
-
npm --prefix examples/marketing run dev
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**`examples/dashboard/`** — the opposite axis from the other three: per-visitor
|
|
75
|
+
**`examples/dashboard/`** — the opposite axis from the other two: per-visitor
|
|
103
76
|
pages. Sign-in with a signed cookie session, a `private: true` protected panel,
|
|
104
77
|
a paginated table fragment, a CSRF-protected mutation form and an island that
|
|
105
78
|
returns a cleanup function. It also has a public landing page, so a cached
|
|
@@ -110,5 +83,5 @@ npm --prefix examples/dashboard install
|
|
|
110
83
|
npm --prefix examples/dashboard run dev
|
|
111
84
|
```
|
|
112
85
|
|
|
113
|
-
In all
|
|
86
|
+
In all three examples, `node smoke.mjs` verifies that the endpoints respond as
|
|
114
87
|
expected while the server is up.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jskelet",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|
|
@@ -135,4 +135,4 @@
|
|
|
135
135
|
"globals": "^16",
|
|
136
136
|
"typescript": "^7.0.2"
|
|
137
137
|
}
|
|
138
|
-
}
|
|
138
|
+
}
|
package/src/config/defaults.js
CHANGED
|
@@ -106,18 +106,34 @@ export const DEFAULT_PREWARM_ON_VISIT = {
|
|
|
106
106
|
/** Sayfa başına üstten alta en fazla kaç link kuyruğa alınır. */
|
|
107
107
|
perPage: 20,
|
|
108
108
|
/**
|
|
109
|
-
* Paralel işçi
|
|
110
|
-
* (
|
|
109
|
+
* Paralel işçi. `null` yalnızca kapalı modda durur; açıkken tavan
|
|
110
|
+
* (`ON_VISIT_CONCURRENCY_CEILING`) uygulanır. Klasik prewarm'daki
|
|
111
|
+
* prod 4 burada geçerli değil — onVisit sürekli çalışır.
|
|
111
112
|
* @type {number | null}
|
|
112
113
|
*/
|
|
113
114
|
concurrency: null,
|
|
114
115
|
/**
|
|
115
|
-
* Saniyedeki istek tavan
|
|
116
|
+
* Saniyedeki istek tavanı. `null` yalnızca kapalı modda durur; açıkken
|
|
117
|
+
* `0` (sınırsız) dahil her şey `ON_VISIT_RPS_CEILING` ile kesilir.
|
|
116
118
|
* @type {number | null}
|
|
117
119
|
*/
|
|
118
120
|
rps: null,
|
|
119
121
|
};
|
|
120
122
|
|
|
123
|
+
/**
|
|
124
|
+
* onVisit sürekli tur olduğu için klasik prewarm'daki "sınırsız" burada yok.
|
|
125
|
+
* Config daha yükseğini yazsa da çözümlenen değer bu tavanları geçemez.
|
|
126
|
+
*/
|
|
127
|
+
export const ON_VISIT_PER_PAGE_CEILING = 20;
|
|
128
|
+
export const ON_VISIT_RPS_CEILING = 2;
|
|
129
|
+
export const ON_VISIT_CONCURRENCY_CEILING = 2;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Bekleyen onVisit yolları. Bir sayfa düzinelerce link basınca kuyruk
|
|
133
|
+
* birikmesin; taşan link bu turda alınmaz.
|
|
134
|
+
*/
|
|
135
|
+
export const ON_VISIT_QUEUE_MAX = 64;
|
|
136
|
+
|
|
121
137
|
/** `onVisit` açıkken `cache().prewarm` kökünde yasak olan klasik alanlar. */
|
|
122
138
|
export const CLASSIC_PREWARM_KEYS = [
|
|
123
139
|
"enabled",
|
|
@@ -136,9 +152,26 @@ export const CLASSIC_PREWARM_KEYS = [
|
|
|
136
152
|
* HTML önbelleğinin girdi sınırı. 500 girdi ortalama bir sayfa boyutunda
|
|
137
153
|
* yaklaşık 100-200 MB tutar; uzun kuyruklu siteler bunu yükseltmek yerine
|
|
138
154
|
* veri önbelleğine yaslanmalı (bkz. `DEFAULT_DATA_CACHE`).
|
|
155
|
+
*
|
|
156
|
+
* Config daha yükseğini istese de girdi sayısı `HTML_CACHE_MAX_ENTRIES_CEILING`
|
|
157
|
+
* değerini geçemez. Asıl bellek freni bayt bütçesidir: şişman sayfa ve
|
|
158
|
+
* `vary.host` kopyası sayı tavanının altında da RSS'i şişirir.
|
|
139
159
|
*/
|
|
140
160
|
export const DEFAULT_HTML_CACHE_MAX_ENTRIES = 500;
|
|
141
161
|
|
|
162
|
+
/** `cache().maxEntries` için sert tavan. Üstü uyarıyla bu değere çekilir. */
|
|
163
|
+
export const HTML_CACHE_MAX_ENTRIES_CEILING = 800;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Süreç içi HTML string + sıkıştırılmış gövde tavanı (256 MB).
|
|
167
|
+
* `install()` bunu uygular; config yükseltemez. Tek sayfa bütçeden büyükse
|
|
168
|
+
* saklanmaz, yanıt yine gider.
|
|
169
|
+
*/
|
|
170
|
+
export const HTML_CACHE_BYTE_BUDGET = 256 * 1024 * 1024;
|
|
171
|
+
|
|
172
|
+
/** `cache().data.maxEntries` için sert tavan. Uzun kuyruk burada durur, HTML'de değil. */
|
|
173
|
+
export const DATA_CACHE_MAX_ENTRIES_CEILING = 20_000;
|
|
174
|
+
|
|
142
175
|
/**
|
|
143
176
|
* `notFound()` geçici bir upstream hatasına denk geldiğinde sayfanın kaç kez
|
|
144
177
|
* daha denenmesi gerektiği.
|