jskelet 0.6.17 → 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 +4 -0
- package/docs/09-dev-araclari.md +8 -0
- package/docs/en/09-dev-tools.md +8 -0
- package/package.json +1 -1
- package/src/client/devtools/overlay.js +155 -2
- package/src/server/dev/devtools.js +47 -1
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,10 @@ 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`.
|
|
20
24
|
- On-demand HTML cache via `cache().prewarm: false`: visited pages still
|
|
21
25
|
write to L1 and Redis/disk L2 with the usual `html` TTLs, but classic
|
|
22
26
|
prewarm, `onVisit` link warming, and early-expiry HTTP refresh stay off
|
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/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
|
|
|
@@ -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
|
|