jskelet 0.6.2 → 0.6.3
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 +136 -136
- package/CHANGELOG.md +620 -596
- package/LICENSE +21 -21
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -309
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +661 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1443 -1423
- package/docs/07-yapilandirma.md +12 -6
- package/docs/08-build.md +429 -428
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +338 -338
- package/docs/12-panel-ve-oturum.md +478 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -328
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +669 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1453 -1431
- package/docs/en/07-configuration.md +1219 -1214
- package/docs/en/08-build.md +447 -446
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +340 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +488 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +17 -1
- package/src/config/index.js +13 -0
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +230 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -462
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -0
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1122
- package/src/server/image-optimizer.js +500 -407
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -66
- package/src/server/logs/pipeline.js +165 -158
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -100
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +356 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1058
- package/src/server/redis.js +588 -569
- package/src/server/render.js +4 -4
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +15 -1
- package/types/config/index.d.ts +8 -0
- package/types/server/cache-blob.d.ts +13 -0
- package/types/server/data-cache.d.ts +9 -0
- package/types/server/disk-cache.d.ts +36 -0
- package/types/server/html-cache.d.ts +26 -3
- package/types/server/logs/file-sink.d.ts +16 -5
- package/types/server/redis.d.ts +2 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis ve disk L2'nin ortak gövdesi.
|
|
3
|
+
*
|
|
4
|
+
* 1 KB altı düz JSON kalır (`redis-cli` ve küçük veri kayıtları okunaklı
|
|
5
|
+
* kalsın). Üstü `JSK\\x01` + brotli. zstd `node:zlib`'de 22.15'ten itibaren
|
|
6
|
+
* var; motor `>=22` olduğu için brotli — yanıt sıkıştırmasıyla aynı kalite.
|
|
7
|
+
*/
|
|
8
|
+
import zlib from "node:zlib";
|
|
9
|
+
|
|
10
|
+
const COMPRESS_THRESHOLD = 1024;
|
|
11
|
+
const CACHE_MAGIC = Buffer.from([0x4a, 0x53, 0x4b, 0x01]);
|
|
12
|
+
const BROTLI_OPTIONS = {
|
|
13
|
+
params: {
|
|
14
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: 5,
|
|
15
|
+
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} json
|
|
21
|
+
* @param {Buffer} compressed
|
|
22
|
+
* @returns {string | Buffer}
|
|
23
|
+
*/
|
|
24
|
+
function pickPayload(json, compressed) {
|
|
25
|
+
const packed = Buffer.concat([CACHE_MAGIC, compressed]);
|
|
26
|
+
return packed.length < Buffer.byteLength(json) ? packed : json;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Küçük gövde düz `string` döner (çağıran senkron yazabilsin). Büyük gövde
|
|
31
|
+
* brotli bitince çözülen bir Promise.
|
|
32
|
+
*
|
|
33
|
+
* @param {unknown} value
|
|
34
|
+
* @returns {string | Buffer | Promise<string | Buffer> | null}
|
|
35
|
+
*/
|
|
36
|
+
export function encodeCacheValue(value) {
|
|
37
|
+
/** @type {string} */
|
|
38
|
+
let json;
|
|
39
|
+
try {
|
|
40
|
+
json = JSON.stringify(value);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (Buffer.byteLength(json) < COMPRESS_THRESHOLD) return json;
|
|
46
|
+
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
zlib.brotliCompress(Buffer.from(json), BROTLI_OPTIONS, (error, compressed) => {
|
|
49
|
+
if (error) resolve(json);
|
|
50
|
+
else resolve(pickPayload(json, compressed));
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Buffer | string} raw
|
|
57
|
+
* @returns {unknown}
|
|
58
|
+
*/
|
|
59
|
+
export function decodeCacheValue(raw) {
|
|
60
|
+
if (
|
|
61
|
+
Buffer.isBuffer(raw) &&
|
|
62
|
+
raw.length > CACHE_MAGIC.length &&
|
|
63
|
+
raw.subarray(0, CACHE_MAGIC.length).equals(CACHE_MAGIC)
|
|
64
|
+
) {
|
|
65
|
+
return JSON.parse(zlib.brotliDecompressSync(raw.subarray(CACHE_MAGIC.length)).toString("utf8"));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : raw;
|
|
69
|
+
return JSON.parse(text);
|
|
70
|
+
}
|
package/src/server/cache-deps.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bir render'ın hangi veri anahtarlarını okuduğunu kaydeder.
|
|
3
|
-
*
|
|
4
|
-
* `request-cache.js` ile aynı desen: `AsyncLocalStorage`, bağlam yoksa her şey
|
|
5
|
-
* sessizce devre dışı. Script'ten, cron'dan ya da istek dışı bir yerden yapılan
|
|
6
|
-
* `withDataCache` çağrıları hiçbir şeye yazılmaz.
|
|
7
|
-
*
|
|
8
|
-
* Neden gerekli: HTML önbelleğinin elinde "bu sayfa şu içerikten etkilenir"
|
|
9
|
-
* bilgisi yoktu, dolayısıyla bir içerik güncellendiğinde tek seçenek TTL'i
|
|
10
|
-
* beklemek ya da tüm önbelleği boşaltmaktı. Bağımlılığı uygulamanın elle
|
|
11
|
-
* bildirmesi (tag'lemek) ise en sık yapılan hatayı davet ediyor: detay
|
|
12
|
-
* sayfasını işaretleyip aynı içeriği listeleyen ana sayfayı unutmak.
|
|
13
|
-
*
|
|
14
|
-
* Burada bildirim yok, **gözlem** var: render sırasında fiilen okunan anahtarlar
|
|
15
|
-
* kaydedilir. Ana sayfa o veriyi okuduysa listede olur, okumadıysa olmaz.
|
|
16
|
-
*/
|
|
17
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
|
-
|
|
19
|
-
/** @type {AsyncLocalStorage<Set<string>>} */
|
|
20
|
-
const storage = new AsyncLocalStorage();
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* `run`'ı, içindeki `recordDependency()` çağrılarının `deps`'e yazacağı bir
|
|
24
|
-
* bağlamda çalıştırır.
|
|
25
|
-
*
|
|
26
|
-
* @template T
|
|
27
|
-
* @param {Set<string>} deps
|
|
28
|
-
* @param {() => T} run
|
|
29
|
-
* @returns {T}
|
|
30
|
-
*/
|
|
31
|
-
export function collectDependencies(deps, run) {
|
|
32
|
-
return storage.run(deps, run);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Bu render'ın bir veri anahtarını okuduğunu bildirir. Bağlam yoksa no-op.
|
|
37
|
-
*
|
|
38
|
-
* @param {string} key
|
|
39
|
-
*/
|
|
40
|
-
export function recordDependency(key) {
|
|
41
|
-
storage.getStore()?.add(key);
|
|
42
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Bir render'ın hangi veri anahtarlarını okuduğunu kaydeder.
|
|
3
|
+
*
|
|
4
|
+
* `request-cache.js` ile aynı desen: `AsyncLocalStorage`, bağlam yoksa her şey
|
|
5
|
+
* sessizce devre dışı. Script'ten, cron'dan ya da istek dışı bir yerden yapılan
|
|
6
|
+
* `withDataCache` çağrıları hiçbir şeye yazılmaz.
|
|
7
|
+
*
|
|
8
|
+
* Neden gerekli: HTML önbelleğinin elinde "bu sayfa şu içerikten etkilenir"
|
|
9
|
+
* bilgisi yoktu, dolayısıyla bir içerik güncellendiğinde tek seçenek TTL'i
|
|
10
|
+
* beklemek ya da tüm önbelleği boşaltmaktı. Bağımlılığı uygulamanın elle
|
|
11
|
+
* bildirmesi (tag'lemek) ise en sık yapılan hatayı davet ediyor: detay
|
|
12
|
+
* sayfasını işaretleyip aynı içeriği listeleyen ana sayfayı unutmak.
|
|
13
|
+
*
|
|
14
|
+
* Burada bildirim yok, **gözlem** var: render sırasında fiilen okunan anahtarlar
|
|
15
|
+
* kaydedilir. Ana sayfa o veriyi okuduysa listede olur, okumadıysa olmaz.
|
|
16
|
+
*/
|
|
17
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
|
+
|
|
19
|
+
/** @type {AsyncLocalStorage<Set<string>>} */
|
|
20
|
+
const storage = new AsyncLocalStorage();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `run`'ı, içindeki `recordDependency()` çağrılarının `deps`'e yazacağı bir
|
|
24
|
+
* bağlamda çalıştırır.
|
|
25
|
+
*
|
|
26
|
+
* @template T
|
|
27
|
+
* @param {Set<string>} deps
|
|
28
|
+
* @param {() => T} run
|
|
29
|
+
* @returns {T}
|
|
30
|
+
*/
|
|
31
|
+
export function collectDependencies(deps, run) {
|
|
32
|
+
return storage.run(deps, run);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Bu render'ın bir veri anahtarını okuduğunu bildirir. Bağlam yoksa no-op.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} key
|
|
39
|
+
*/
|
|
40
|
+
export function recordDependency(key) {
|
|
41
|
+
storage.getStore()?.add(key);
|
|
42
|
+
}
|
package/src/server/cache-vary.js
CHANGED
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML cache anahtarına host / header / özel fn ile sabit vary parçası ekler.
|
|
3
|
-
*
|
|
4
|
-
* CDN zaten tam URL ile ayırır; asıl risk origin L1 ve Redis HTML anahtarı —
|
|
5
|
-
* host'tan locale üreten sitelerde `vary.host: true` olmadan ilk locale'in
|
|
6
|
-
* HTML'i diğer host'a servis edilir.
|
|
7
|
-
*/
|
|
8
|
-
import { getConfig } from "../config/index.js";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Public Host: `x-forwarded-host` (ilk değer) yoksa `Host`. Lowercase, portsuz.
|
|
12
|
-
* IPv6 (`[::1]:3000`) köşeli parantezleri korur.
|
|
13
|
-
*
|
|
14
|
-
* @param {{ headers?: Record<string, unknown>, get?: (name: string) => string | undefined }} req
|
|
15
|
-
* @returns {string}
|
|
16
|
-
*/
|
|
17
|
-
export function publicHost(req) {
|
|
18
|
-
const forwarded = headerValue(req, "x-forwarded-host");
|
|
19
|
-
const raw = forwarded || headerValue(req, "host") || "";
|
|
20
|
-
const first = raw.split(",")[0].trim().toLowerCase();
|
|
21
|
-
return stripPort(first);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @param {string} host
|
|
26
|
-
* @returns {string}
|
|
27
|
-
*/
|
|
28
|
-
function stripPort(host) {
|
|
29
|
-
if (!host) return "";
|
|
30
|
-
if (host.startsWith("[")) {
|
|
31
|
-
const end = host.indexOf("]");
|
|
32
|
-
return end === -1 ? host : host.slice(0, end + 1);
|
|
33
|
-
}
|
|
34
|
-
// Birden fazla `:` → portsuz IPv6 (Host'ta nadir); tek `:` → host:port.
|
|
35
|
-
const colon = host.lastIndexOf(":");
|
|
36
|
-
if (colon === -1) return host;
|
|
37
|
-
if (host.indexOf(":") !== colon) return host;
|
|
38
|
-
return host.slice(0, colon);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @param {{ headers?: Record<string, unknown>, get?: (name: string) => string | undefined }} req
|
|
43
|
-
* @param {string} name
|
|
44
|
-
* @returns {string}
|
|
45
|
-
*/
|
|
46
|
-
function headerValue(req, name) {
|
|
47
|
-
const viaGet = req.get?.(name);
|
|
48
|
-
if (typeof viaGet === "string" && viaGet) return viaGet;
|
|
49
|
-
|
|
50
|
-
const raw = req.headers?.[name.toLowerCase()];
|
|
51
|
-
if (Array.isArray(raw)) return raw[0] ? String(raw[0]) : "";
|
|
52
|
-
if (raw == null) return "";
|
|
53
|
-
return String(raw);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Anahtarın başına eklenen önek: `h=tr.example.com|` veya
|
|
58
|
-
* `h=…&x-locale=tr|`. Vary yoksa boş string.
|
|
59
|
-
*
|
|
60
|
-
* @param {{ headers?: Record<string, unknown>, get?: (name: string) => string | undefined }} [req]
|
|
61
|
-
* @returns {string}
|
|
62
|
-
*/
|
|
63
|
-
export function buildVaryPrefix(req) {
|
|
64
|
-
if (!req) return "";
|
|
65
|
-
|
|
66
|
-
let vary;
|
|
67
|
-
try {
|
|
68
|
-
vary = getConfig().cacheVary;
|
|
69
|
-
} catch {
|
|
70
|
-
return "";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!vary || (!vary.host && !vary.headers.length && !vary.fn)) return "";
|
|
74
|
-
|
|
75
|
-
/** @type {string[]} */
|
|
76
|
-
const parts = [];
|
|
77
|
-
|
|
78
|
-
if (vary.host) {
|
|
79
|
-
const host = publicHost(req);
|
|
80
|
-
if (host) parts.push(`h=${host}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
for (const name of vary.headers) {
|
|
84
|
-
const value = headerValue(req, name).trim();
|
|
85
|
-
if (value) parts.push(`${name}=${value}`);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (typeof vary.fn === "function") {
|
|
89
|
-
try {
|
|
90
|
-
const custom = vary.fn(/** @type {import('express').Request} */ (req));
|
|
91
|
-
if (custom != null && custom !== "") parts.push(String(custom));
|
|
92
|
-
} catch (error) {
|
|
93
|
-
console.warn("[cache] cache().vary.fn threw, ignoring it", error);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return parts.length ? `${parts.join("&")}|` : "";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Anahtardan yol kısmını çıkarır (`[vary|]yol?query` → `yol`).
|
|
102
|
-
* Invalidation hedefleri `/…` ile başlar; vary öneki eşleşmeye karışmamalı.
|
|
103
|
-
*
|
|
104
|
-
* @param {string} key
|
|
105
|
-
* @returns {string}
|
|
106
|
-
*/
|
|
107
|
-
export function pathOfCacheKey(key) {
|
|
108
|
-
const mark = key.indexOf("?");
|
|
109
|
-
const beforeQuery = mark === -1 ? key : key.slice(0, mark);
|
|
110
|
-
const sep = beforeQuery.indexOf("|/");
|
|
111
|
-
if (sep !== -1) return beforeQuery.slice(sep + 1);
|
|
112
|
-
return beforeQuery;
|
|
113
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* HTML cache anahtarına host / header / özel fn ile sabit vary parçası ekler.
|
|
3
|
+
*
|
|
4
|
+
* CDN zaten tam URL ile ayırır; asıl risk origin L1 ve Redis HTML anahtarı —
|
|
5
|
+
* host'tan locale üreten sitelerde `vary.host: true` olmadan ilk locale'in
|
|
6
|
+
* HTML'i diğer host'a servis edilir.
|
|
7
|
+
*/
|
|
8
|
+
import { getConfig } from "../config/index.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Public Host: `x-forwarded-host` (ilk değer) yoksa `Host`. Lowercase, portsuz.
|
|
12
|
+
* IPv6 (`[::1]:3000`) köşeli parantezleri korur.
|
|
13
|
+
*
|
|
14
|
+
* @param {{ headers?: Record<string, unknown>, get?: (name: string) => string | undefined }} req
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function publicHost(req) {
|
|
18
|
+
const forwarded = headerValue(req, "x-forwarded-host");
|
|
19
|
+
const raw = forwarded || headerValue(req, "host") || "";
|
|
20
|
+
const first = raw.split(",")[0].trim().toLowerCase();
|
|
21
|
+
return stripPort(first);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} host
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
function stripPort(host) {
|
|
29
|
+
if (!host) return "";
|
|
30
|
+
if (host.startsWith("[")) {
|
|
31
|
+
const end = host.indexOf("]");
|
|
32
|
+
return end === -1 ? host : host.slice(0, end + 1);
|
|
33
|
+
}
|
|
34
|
+
// Birden fazla `:` → portsuz IPv6 (Host'ta nadir); tek `:` → host:port.
|
|
35
|
+
const colon = host.lastIndexOf(":");
|
|
36
|
+
if (colon === -1) return host;
|
|
37
|
+
if (host.indexOf(":") !== colon) return host;
|
|
38
|
+
return host.slice(0, colon);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {{ headers?: Record<string, unknown>, get?: (name: string) => string | undefined }} req
|
|
43
|
+
* @param {string} name
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function headerValue(req, name) {
|
|
47
|
+
const viaGet = req.get?.(name);
|
|
48
|
+
if (typeof viaGet === "string" && viaGet) return viaGet;
|
|
49
|
+
|
|
50
|
+
const raw = req.headers?.[name.toLowerCase()];
|
|
51
|
+
if (Array.isArray(raw)) return raw[0] ? String(raw[0]) : "";
|
|
52
|
+
if (raw == null) return "";
|
|
53
|
+
return String(raw);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Anahtarın başına eklenen önek: `h=tr.example.com|` veya
|
|
58
|
+
* `h=…&x-locale=tr|`. Vary yoksa boş string.
|
|
59
|
+
*
|
|
60
|
+
* @param {{ headers?: Record<string, unknown>, get?: (name: string) => string | undefined }} [req]
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
export function buildVaryPrefix(req) {
|
|
64
|
+
if (!req) return "";
|
|
65
|
+
|
|
66
|
+
let vary;
|
|
67
|
+
try {
|
|
68
|
+
vary = getConfig().cacheVary;
|
|
69
|
+
} catch {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!vary || (!vary.host && !vary.headers.length && !vary.fn)) return "";
|
|
74
|
+
|
|
75
|
+
/** @type {string[]} */
|
|
76
|
+
const parts = [];
|
|
77
|
+
|
|
78
|
+
if (vary.host) {
|
|
79
|
+
const host = publicHost(req);
|
|
80
|
+
if (host) parts.push(`h=${host}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const name of vary.headers) {
|
|
84
|
+
const value = headerValue(req, name).trim();
|
|
85
|
+
if (value) parts.push(`${name}=${value}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof vary.fn === "function") {
|
|
89
|
+
try {
|
|
90
|
+
const custom = vary.fn(/** @type {import('express').Request} */ (req));
|
|
91
|
+
if (custom != null && custom !== "") parts.push(String(custom));
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn("[cache] cache().vary.fn threw, ignoring it", error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return parts.length ? `${parts.join("&")}|` : "";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Anahtardan yol kısmını çıkarır (`[vary|]yol?query` → `yol`).
|
|
102
|
+
* Invalidation hedefleri `/…` ile başlar; vary öneki eşleşmeye karışmamalı.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} key
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
export function pathOfCacheKey(key) {
|
|
108
|
+
const mark = key.indexOf("?");
|
|
109
|
+
const beforeQuery = mark === -1 ? key : key.slice(0, mark);
|
|
110
|
+
const sep = beforeQuery.indexOf("|/");
|
|
111
|
+
if (sep !== -1) return beforeQuery.slice(sep + 1);
|
|
112
|
+
return beforeQuery;
|
|
113
|
+
}
|