jskelet 0.2.1 → 0.2.2
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 +54 -0
- package/docs/06-cache.md +195 -4
- package/docs/07-yapilandirma.md +70 -2
- package/docs/en/06-caching.md +202 -5
- package/docs/en/07-configuration.md +72 -2
- package/package.json +1 -1
- package/src/client/cache-panel/login.html +63 -0
- package/src/client/cache-panel/panel.css +448 -0
- package/src/client/cache-panel/panel.html +133 -0
- package/src/client/cache-panel/panel.js +339 -0
- package/src/client/devtools/overlay.js +85 -0
- package/src/client/devtools/report.js +13 -0
- package/src/config/defaults.js +67 -2
- package/src/config/index.js +98 -1
- package/src/index.js +5 -0
- package/src/server/cache-panel.js +505 -0
- package/src/server/create-app.js +16 -0
- package/src/server/data-cache.js +92 -2
- package/src/server/dev/report.js +8 -1
- package/src/server/html-cache.js +36 -0
- package/src/server/prewarm.js +94 -15
- package/src/server/upstream-limiter.js +376 -0
- package/src/server/upstream-tracking.js +25 -0
package/src/config/defaults.js
CHANGED
|
@@ -120,6 +120,42 @@ export const DEFAULT_DATA_CACHE = {
|
|
|
120
120
|
staleFactor: 10,
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Upstream API'ye giden isteklerin host başına hız freni.
|
|
125
|
+
*
|
|
126
|
+
* Varsayılan **kapalı** (`rate: 0`): fren, kotasını bilen bir uygulamanın
|
|
127
|
+
* bilinçli kararı. Açıldığında `rate` bir tavan olur ve gerçek hız 429
|
|
128
|
+
* cevaplarına göre kendini aşağı çeker (bkz. `src/server/upstream-limiter.js`).
|
|
129
|
+
*
|
|
130
|
+
* `hosts` ile tek tek uçlar ayrılabilir; API'lerin kotası aynı olmak zorunda
|
|
131
|
+
* değil:
|
|
132
|
+
*
|
|
133
|
+
* upstream: { rate: 10, hosts: { "api.example.com": { rate: 3 } } }
|
|
134
|
+
*/
|
|
135
|
+
export const DEFAULT_UPSTREAM_LIMIT = {
|
|
136
|
+
/** Saniyedeki en fazla çağrı. 0 → fren tamamen kapalı. */
|
|
137
|
+
rate: 0,
|
|
138
|
+
/** Kova boyu; verilmezse bir saniyelik bütçe kadar patlamaya izin verilir. */
|
|
139
|
+
burst: 0,
|
|
140
|
+
/** Aynı anda uçabilecek çağrı. Ortalama hızdan bağımsız: anlık baskıyı bağlar. */
|
|
141
|
+
concurrency: 8,
|
|
142
|
+
/** Azalmanın dibi: hız buranın altına inmez, yoksa site tamamen durur. */
|
|
143
|
+
minRate: 0.5,
|
|
144
|
+
/** Toplamsal artışın adımı (çağrı/saniye) ve periyodu. */
|
|
145
|
+
increaseStep: 1,
|
|
146
|
+
increaseIntervalMs: 5000,
|
|
147
|
+
/**
|
|
148
|
+
* İki azalma arasındaki en kısa süre. Aynı anda uçan on çağrının hepsi 429
|
|
149
|
+
* dönerse hız on kez yarılanıp dibe vurmasın.
|
|
150
|
+
*/
|
|
151
|
+
decreaseIntervalMs: 1000,
|
|
152
|
+
/** Devre kesici: art arda kaç 429'dan sonra host'a hiç gidilmeyeceği. */
|
|
153
|
+
breakerFailures: 5,
|
|
154
|
+
breakerCooldownMs: 10_000,
|
|
155
|
+
/** @type {Record<string, Record<string, number>>} */
|
|
156
|
+
hosts: {},
|
|
157
|
+
};
|
|
158
|
+
|
|
123
159
|
/**
|
|
124
160
|
* Opsiyonel Redis ikinci kademesi (L2).
|
|
125
161
|
*
|
|
@@ -155,8 +191,37 @@ export const DEFAULT_REDIS = {
|
|
|
155
191
|
commandTimeoutMs: 200,
|
|
156
192
|
};
|
|
157
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Önbellek yönetim paneli.
|
|
196
|
+
*
|
|
197
|
+
* `enabled` varsayılan olarak **kapalı** ve ortama bakmaz: panel açıldığında
|
|
198
|
+
* production'da da çalışır, ama açılması bilinçli bir karar olmalı. Kapalıyken
|
|
199
|
+
* router hiç mount edilmez — yolun kendisi de yok, yani 404 dönen bir uç bile
|
|
200
|
+
* ortaya çıkmaz.
|
|
201
|
+
*
|
|
202
|
+
* Şifre her süreç başlangıcında yeniden üretilir (bkz.
|
|
203
|
+
* `src/server/cache-panel.js`): panelin ömrü sürecin ömrü kadardır ve bir
|
|
204
|
+
* deploy eski erişimi otomatik olarak iptal eder. Bu yüzden config'te şifre
|
|
205
|
+
* alanı yok.
|
|
206
|
+
*/
|
|
207
|
+
export const DEFAULT_CACHE_PANEL = {
|
|
208
|
+
enabled: false,
|
|
209
|
+
basePath: "/_jskelet/cache",
|
|
210
|
+
/** Kaç başarısız denemeden sonra IP yasaklanır. */
|
|
211
|
+
banAttempts: 3,
|
|
212
|
+
/** Yasağın süresi. */
|
|
213
|
+
banHours: 24,
|
|
214
|
+
/** Oturumun ömrü; süreç yeniden başladığında zaten sıfırlanır. */
|
|
215
|
+
sessionHours: 12,
|
|
216
|
+
};
|
|
217
|
+
|
|
158
218
|
/** Oturuma bağlı sayfalar ısıtılmaz; uygulama kendi listesini verebilir. */
|
|
159
|
-
export const DEFAULT_PREWARM_SKIP = [
|
|
219
|
+
export const DEFAULT_PREWARM_SKIP = [
|
|
220
|
+
"/api/",
|
|
221
|
+
"/_fragment/",
|
|
222
|
+
"/__jskelet/",
|
|
223
|
+
"/_jskelet/",
|
|
224
|
+
];
|
|
160
225
|
|
|
161
226
|
/**
|
|
162
227
|
* Site içi gezinme ipuçları (Speculation Rules + view transition).
|
|
@@ -182,7 +247,7 @@ export const DEFAULT_NAVIGATION = {
|
|
|
182
247
|
* hedefi olmayan uçların spekülatif istekle tetiklenmesi gerçek bir hata
|
|
183
248
|
* kaynağı; uygulama kendi listesini `navigation.exclude` ile ekler.
|
|
184
249
|
*/
|
|
185
|
-
export const DEFAULT_NAVIGATION_EXCLUDE = ["/api/*", "/_fragment/*"];
|
|
250
|
+
export const DEFAULT_NAVIGATION_EXCLUDE = ["/api/*", "/_fragment/*", "/_jskelet/*"];
|
|
186
251
|
|
|
187
252
|
/**
|
|
188
253
|
* Güvenlik ayarları.
|
package/src/config/index.js
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* redirects() → [{ source, destination, permanent?, statusCode? }]
|
|
17
17
|
* rewrites() → [{ source, destination }] | { beforeFiles?, afterFiles? }
|
|
18
18
|
* cache() → { html?: { [source]: saniye }, maxEntries?: number,
|
|
19
|
-
* data?: {...}, redis?: {...}, prewarm?: {...}
|
|
19
|
+
* data?: {...}, redis?: {...}, prewarm?: {...},
|
|
20
|
+
* panel?: {...} }
|
|
20
21
|
*
|
|
21
22
|
* Fonksiyon olmayan bölümler (`brand`, `security`, `static`, `navigation`…)
|
|
22
23
|
* düz nesne olarak okunur.
|
|
@@ -28,6 +29,7 @@ import { pathToFileURL } from "node:url";
|
|
|
28
29
|
import { compilePattern, matchPattern } from "./pattern.js";
|
|
29
30
|
import {
|
|
30
31
|
DEFAULT_BRAND,
|
|
32
|
+
DEFAULT_CACHE_PANEL,
|
|
31
33
|
DEFAULT_DATA_CACHE,
|
|
32
34
|
DEFAULT_DEV_GATE_BYPASS,
|
|
33
35
|
DEFAULT_DIRS,
|
|
@@ -40,6 +42,7 @@ import {
|
|
|
40
42
|
DEFAULT_SECURITY,
|
|
41
43
|
DEFAULT_STATIC,
|
|
42
44
|
DEFAULT_TRANSIENT_RETRY,
|
|
45
|
+
DEFAULT_UPSTREAM_LIMIT,
|
|
43
46
|
} from "./defaults.js";
|
|
44
47
|
|
|
45
48
|
/** Framework paketinin kökü — kendi şablonlarına ve varlıklarına erişir. */
|
|
@@ -87,6 +90,8 @@ const CONFIG_FILE = "jskelet.config.mjs";
|
|
|
87
90
|
* @property {boolean} trackDependencies Render'ın okuduğu veri anahtarları kaydedilsin mi.
|
|
88
91
|
* @property {{ attempts: number, delayMs: number }} transientRetry
|
|
89
92
|
* @property {RedisConfig} redis Opsiyonel Redis ikinci kademesi.
|
|
93
|
+
* @property {typeof DEFAULT_UPSTREAM_LIMIT} upstream Upstream hız freni.
|
|
94
|
+
* @property {typeof DEFAULT_CACHE_PANEL} cachePanel Önbellek yönetim paneli.
|
|
90
95
|
* @property {Record<string, unknown>} prewarm
|
|
91
96
|
* @property {{ source: string, test: (pathname: string) => boolean }[]} prewarmPriority
|
|
92
97
|
* @property {Record<string, unknown>} brand
|
|
@@ -249,6 +254,90 @@ function normalizeRedis(raw) {
|
|
|
249
254
|
};
|
|
250
255
|
}
|
|
251
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Upstream hız freni. Sayısal alanlar tipine zorlanır; bozuk bir değer freni
|
|
259
|
+
* yanlış ayarlamak yerine varsayılana döner.
|
|
260
|
+
*
|
|
261
|
+
* @param {unknown} raw
|
|
262
|
+
* @returns {typeof DEFAULT_UPSTREAM_LIMIT}
|
|
263
|
+
*/
|
|
264
|
+
function normalizeUpstream(raw) {
|
|
265
|
+
const source = /** @type {Record<string, any>} */ (raw ?? {});
|
|
266
|
+
const merged = { ...DEFAULT_UPSTREAM_LIMIT, ...source };
|
|
267
|
+
|
|
268
|
+
/** @param {string} key */
|
|
269
|
+
const positive = (key) => {
|
|
270
|
+
const value = Number(merged[key]);
|
|
271
|
+
return Number.isFinite(value) && value >= 0
|
|
272
|
+
? value
|
|
273
|
+
: /** @type {any} */ (DEFAULT_UPSTREAM_LIMIT)[key];
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/** @type {Record<string, Record<string, number>>} */
|
|
277
|
+
const hosts = {};
|
|
278
|
+
for (const [host, override] of Object.entries(merged.hosts ?? {})) {
|
|
279
|
+
if (override && typeof override === "object") hosts[host] = override;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
...merged,
|
|
284
|
+
rate: positive("rate"),
|
|
285
|
+
burst: positive("burst"),
|
|
286
|
+
concurrency: Math.max(1, Math.floor(positive("concurrency"))),
|
|
287
|
+
minRate: positive("minRate"),
|
|
288
|
+
increaseStep: positive("increaseStep"),
|
|
289
|
+
increaseIntervalMs: positive("increaseIntervalMs"),
|
|
290
|
+
decreaseIntervalMs: positive("decreaseIntervalMs"),
|
|
291
|
+
breakerFailures: Math.floor(positive("breakerFailures")),
|
|
292
|
+
breakerCooldownMs: positive("breakerCooldownMs"),
|
|
293
|
+
hosts,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Önbellek panelinin bölümü.
|
|
299
|
+
*
|
|
300
|
+
* `enabled` yalnızca açıkça `true` verildiğinde ya da `JSKELET_CACHE_PANEL`
|
|
301
|
+
* ortam değişkeni ayarlandığında açılır: paneli yanlışlıkla açmanın bedeli,
|
|
302
|
+
* önbelleği boşaltabilen bir ucu internete koymak.
|
|
303
|
+
*
|
|
304
|
+
* Ortam değişkeni config'in **üstünde** duruyor, çünkü paneli genelde bir
|
|
305
|
+
* arıza sırasında tek seferlik açmak isteniyor ve o an config dosyasını
|
|
306
|
+
* değiştirip yeniden dağıtmak istenmiyor. `JSKELET_CACHE_PANEL=0` aynı
|
|
307
|
+
* mantıkla config'te açık olan paneli kapatır.
|
|
308
|
+
*
|
|
309
|
+
* @param {unknown} raw
|
|
310
|
+
* @returns {typeof DEFAULT_CACHE_PANEL}
|
|
311
|
+
*/
|
|
312
|
+
function normalizeCachePanel(raw) {
|
|
313
|
+
const source = /** @type {Record<string, any>} */ (raw ?? {});
|
|
314
|
+
const env = process.env.JSKELET_CACHE_PANEL;
|
|
315
|
+
|
|
316
|
+
const basePath =
|
|
317
|
+
typeof source.basePath === "string" && source.basePath.startsWith("/")
|
|
318
|
+
? source.basePath.replace(/\/+$/, "")
|
|
319
|
+
: DEFAULT_CACHE_PANEL.basePath;
|
|
320
|
+
|
|
321
|
+
/** @param {string} key @param {number} min */
|
|
322
|
+
const positive = (key, min) => {
|
|
323
|
+
const value = Number(source[key]);
|
|
324
|
+
return Number.isFinite(value) && value >= min
|
|
325
|
+
? value
|
|
326
|
+
: /** @type {any} */ (DEFAULT_CACHE_PANEL)[key];
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
enabled:
|
|
331
|
+
env === undefined
|
|
332
|
+
? source.enabled === true
|
|
333
|
+
: env !== "0" && env !== "false" && env !== "",
|
|
334
|
+
basePath: basePath || DEFAULT_CACHE_PANEL.basePath,
|
|
335
|
+
banAttempts: Math.floor(positive("banAttempts", 1)),
|
|
336
|
+
banHours: positive("banHours", 0),
|
|
337
|
+
sessionHours: positive("sessionHours", 0),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
252
341
|
/**
|
|
253
342
|
* @param {unknown} raw
|
|
254
343
|
* @returns {{ html: ResolvedConfig["html"], htmlMaxEntries: number,
|
|
@@ -256,6 +345,8 @@ function normalizeRedis(raw) {
|
|
|
256
345
|
* trackDependencies: boolean,
|
|
257
346
|
* transientRetry: { attempts: number, delayMs: number },
|
|
258
347
|
* redis: RedisConfig,
|
|
348
|
+
* upstream: typeof DEFAULT_UPSTREAM_LIMIT,
|
|
349
|
+
* cachePanel: typeof DEFAULT_CACHE_PANEL,
|
|
259
350
|
* prewarm: Record<string, unknown>,
|
|
260
351
|
* prewarmPriority: ResolvedConfig["prewarmPriority"] }}
|
|
261
352
|
*/
|
|
@@ -292,6 +383,8 @@ function normalizeCache(raw) {
|
|
|
292
383
|
? { attempts: 0, delayMs: 0 }
|
|
293
384
|
: { ...DEFAULT_TRANSIENT_RETRY, ...(raw?.transientRetry ?? {}) },
|
|
294
385
|
redis: normalizeRedis(raw?.redis),
|
|
386
|
+
upstream: normalizeUpstream(raw?.upstream),
|
|
387
|
+
cachePanel: normalizeCachePanel(raw?.panel),
|
|
295
388
|
// Desenler derlenmiş hâlde ayrı alanda tutulur: `prewarm` sayısal
|
|
296
389
|
// ayarların düz torbası olarak kalsın, her turda yeniden derlenmesin.
|
|
297
390
|
prewarm,
|
|
@@ -510,6 +603,8 @@ export async function loadConfig(options = {}) {
|
|
|
510
603
|
trackDependencies,
|
|
511
604
|
transientRetry,
|
|
512
605
|
redis,
|
|
606
|
+
upstream,
|
|
607
|
+
cachePanel,
|
|
513
608
|
prewarm,
|
|
514
609
|
prewarmPriority,
|
|
515
610
|
} = normalizeCache(cache);
|
|
@@ -530,6 +625,8 @@ export async function loadConfig(options = {}) {
|
|
|
530
625
|
trackDependencies,
|
|
531
626
|
transientRetry,
|
|
532
627
|
redis,
|
|
628
|
+
upstream,
|
|
629
|
+
cachePanel,
|
|
533
630
|
prewarm,
|
|
534
631
|
prewarmPriority,
|
|
535
632
|
brand,
|
package/src/index.js
CHANGED
|
@@ -41,6 +41,7 @@ export { headHints } from "./server/head-hints.js";
|
|
|
41
41
|
export { renderHeadMeta } from "./server/metadata.js";
|
|
42
42
|
export {
|
|
43
43
|
clearHtmlCache,
|
|
44
|
+
dropHtmlCacheKey,
|
|
44
45
|
getHtmlCacheEntries,
|
|
45
46
|
getHtmlCacheSize,
|
|
46
47
|
invalidateHtmlCache,
|
|
@@ -49,13 +50,17 @@ export {
|
|
|
49
50
|
export {
|
|
50
51
|
clearDataCache,
|
|
51
52
|
dataCache,
|
|
53
|
+
dropDataCacheKey,
|
|
52
54
|
getDataCacheEntries,
|
|
53
55
|
getDataCacheSize,
|
|
56
|
+
getDataCacheStats,
|
|
54
57
|
withDataCache,
|
|
55
58
|
} from "./server/data-cache.js";
|
|
56
59
|
// Paylaşımlı önbellek kademesinin durumu. Bağlantı kurulmamışken de güvenle
|
|
57
60
|
// çağrılır; healthcheck uçları bunu okuyor.
|
|
58
61
|
export { getRedisStatus } from "./server/redis.js";
|
|
62
|
+
// Upstream hız freninin host başına durumu: healthcheck ve teşhis uçları için.
|
|
63
|
+
export { getUpstreamLimiterStatus } from "./server/upstream-limiter.js";
|
|
59
64
|
export { prewarm, prewarmProgress } from "./server/prewarm.js";
|
|
60
65
|
export { createProxy } from "./server/middleware/upstream-proxy.js";
|
|
61
66
|
export { getConfig, loadConfig } from "./config/index.js";
|