jskelet 0.2.5 → 0.3.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/AGENTS.md +132 -132
- package/CHANGELOG.md +403 -383
- package/LICENSE +21 -21
- package/bin/jskelet.mjs +103 -103
- package/docs/01-baslangic.md +285 -285
- package/docs/02-mimari.md +293 -287
- package/docs/03-routing.md +486 -480
- package/docs/04-render-ve-sablonlar.md +490 -490
- package/docs/05-islands.md +482 -482
- package/docs/06-cache.md +1231 -1209
- package/docs/07-yapilandirma.md +44 -21
- package/docs/08-build.md +366 -366
- package/docs/09-dev-araclari.md +335 -335
- package/docs/10-dagitim.md +329 -329
- package/docs/11-tasima.md +1 -0
- package/docs/12-panel-ve-oturum.md +384 -384
- package/docs/README.md +105 -105
- package/docs/en/01-getting-started.md +292 -292
- package/docs/en/02-architecture.md +311 -305
- package/docs/en/03-routing.md +503 -497
- package/docs/en/04-rendering.md +504 -504
- package/docs/en/05-islands.md +492 -492
- package/docs/en/06-caching.md +1197 -1198
- package/docs/en/07-configuration.md +1009 -986
- package/docs/en/08-build.md +383 -383
- package/docs/en/09-dev-tools.md +342 -342
- package/docs/en/10-deployment.md +332 -332
- package/docs/en/11-migration.md +360 -359
- package/docs/en/12-dashboards-and-sessions.md +392 -392
- package/docs/en/README.md +112 -112
- package/package.json +102 -102
- package/src/build/ensure-build.mjs +15 -15
- package/src/build/paths.mjs +143 -143
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +268 -268
- package/src/build/tasks/css.mjs +124 -124
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +224 -224
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/client/{cache-panel → admin}/i18n.js +756 -670
- package/src/client/{cache-panel → admin}/login.html +74 -74
- package/src/client/{cache-panel → admin}/panel.css +804 -756
- package/src/client/admin/panel.html +486 -0
- package/src/client/{cache-panel → admin}/panel.js +1242 -915
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +725 -725
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +35 -35
- package/src/client/registry.js +297 -297
- package/src/client/safe-image.js +91 -91
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/config/defaults.js +16 -7
- package/src/config/index.js +38 -22
- package/src/config/pattern.js +107 -107
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies.js +257 -257
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +162 -162
- package/src/index.js +83 -83
- package/src/init.mjs +221 -221
- package/src/log.mjs +58 -0
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -0
- package/src/server/admin/auth.js +125 -0
- package/src/server/admin/event-log.js +151 -0
- package/src/server/admin/gate.js +209 -0
- package/src/server/admin/inventory.js +188 -0
- package/src/server/admin/mount.js +56 -0
- package/src/server/admin/router.js +216 -0
- package/src/server/admin/snapshot.js +126 -0
- package/src/server/assets.js +147 -147
- package/src/server/cache-deps.js +42 -42
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +295 -291
- package/src/server/data-cache.js +462 -462
- package/src/server/dev/report.js +369 -369
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/html-cache.js +817 -817
- 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 +62 -62
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/static-precompressed.js +100 -100
- package/src/server/middleware/trailing-slash.js +53 -0
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/prewarm.js +601 -601
- package/src/server/redis.js +569 -569
- package/src/server/router.js +128 -128
- package/src/server/status-page.js +164 -164
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/start.mjs +7 -7
- package/src/templates/layout.ejs +44 -44
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +85 -85
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +245 -245
- package/src/client/cache-panel/panel.html +0 -308
- package/src/server/cache-panel.js +0 -759
|
@@ -1,166 +1,166 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Render başına upstream API hatalarını toplar.
|
|
3
|
-
*
|
|
4
|
-
* `render.js` her sayfayı bu bağlam içinde üretir. Böylece HTML önbelleği "bu
|
|
5
|
-
* çıktı eksik veriyle üretildi" bilgisine sahip olur ve bozuk sayfayı saklamaz;
|
|
6
|
-
* `notFound()` de geçici bir hataya denk geldiğinde 404 olmaktan çıkar.
|
|
7
|
-
*
|
|
8
|
-
* Bilgi iki yoldan gelir:
|
|
9
|
-
*
|
|
10
|
-
* 1. **Otomatik** — `trackUpstreamFetch()` `globalThis.fetch`i sarar ve
|
|
11
|
-
* geçici hataları (429, 5xx, ağ) kendiliğinden bildirir. `createApp()`
|
|
12
|
-
* bunu açılışta kurar, yani hiçbir uygulama kodu gerekmez.
|
|
13
|
-
* 2. **Elle** — `fetch` kullanmayan bir istemci (veritabanı sürücüsü, gRPC,
|
|
14
|
-
* SDK) için:
|
|
15
|
-
*
|
|
16
|
-
* import { reportUpstreamFailure } from "jskelet";
|
|
17
|
-
*
|
|
18
|
-
* if (!response.ok) {
|
|
19
|
-
* reportUpstreamFailure({ status: response.status, path: url });
|
|
20
|
-
* }
|
|
21
|
-
*
|
|
22
|
-
* İki yol aynı hatayı bildirirse tekilleştirilir.
|
|
23
|
-
*
|
|
24
|
-
* Sarmalayıcı aynı zamanda hız freninin durduğu yerdir
|
|
25
|
-
* (`upstream-limiter.js`): kotayı harcayan şey sayfa isteği değil, buradan
|
|
26
|
-
* geçen çağrı. Fren varsayılan olarak kapalı.
|
|
27
|
-
*/
|
|
28
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
29
|
-
import { limitUpstream, noteUpstreamResponse } from "./upstream-limiter.js";
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @typedef {{ status: number, path: string }} UpstreamFailure
|
|
33
|
-
* `status: 0` ağ hatası anlamına gelir (yanıt hiç gelmedi).
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
/** @type {AsyncLocalStorage<{ failures: UpstreamFailure[] }>} */
|
|
37
|
-
const storage = new AsyncLocalStorage();
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @param {UpstreamFailure} failure
|
|
41
|
-
* @returns {void}
|
|
42
|
-
*/
|
|
43
|
-
export function reportUpstreamFailure(failure) {
|
|
44
|
-
const store = storage.getStore();
|
|
45
|
-
if (!store) return;
|
|
46
|
-
|
|
47
|
-
// Aynı hatayı hem otomatik sarmalayıcı hem uygulamanın istemcisi
|
|
48
|
-
// bildirebilir; aynı satırı iki kez loglamanın faydası yok.
|
|
49
|
-
const duplicate = store.failures.some(
|
|
50
|
-
(existing) => existing.status === failure.status && existing.path === failure.path,
|
|
51
|
-
);
|
|
52
|
-
if (!duplicate) store.failures.push(failure);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Geçici sayılan durumlar: tekrar denemekle düzelebilenler. Bu liste
|
|
57
|
-
* `render.js` ile paylaşılır — hangi hatanın önbelleği engellediği ve hangi
|
|
58
|
-
* hatanın `notFound()`u 404 olmaktan çıkardığı tek yerde tanımlı olsun.
|
|
59
|
-
*/
|
|
60
|
-
const TRANSIENT_STATUSES = new Set([0, 408, 425, 429]);
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @param {number} status
|
|
64
|
-
* @returns {boolean}
|
|
65
|
-
*/
|
|
66
|
-
export function isTransientStatus(status) {
|
|
67
|
-
return TRANSIENT_STATUSES.has(status) || status >= 500;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* `globalThis.fetch`i sarıp **geçici** upstream hatalarını kendiliğinden
|
|
72
|
-
* bildirir.
|
|
73
|
-
*
|
|
74
|
-
* Gerekçesi pratik: `reportUpstreamFailure()` sözleşmesi uygulamanın HTTP
|
|
75
|
-
* istemcisine bir satır eklemeyi gerektiriyor ve o satır yazılmadığında
|
|
76
|
-
* framework rate limit'i hiç göremiyor — veri gelmediği için `notFound()`
|
|
77
|
-
* çağıran sayfa 404 olarak servis ediliyordu. Otomatik izleme bu bilgiyi
|
|
78
|
-
* varsayılan hâle getirir; elle çağrı hâlâ geçerli ve tekilleştirilir.
|
|
79
|
-
*
|
|
80
|
-
* Yalnızca geçici durumlar bildirilir. `404`/`403` gibi deterministik
|
|
81
|
-
* cevaplar birçok API'de "böyle bir kayıt yok" anlamına geliyor ve onları
|
|
82
|
-
* otomatik olarak "eksik veri" saymak her sayfada yanlış uyarı üretirdi.
|
|
83
|
-
*
|
|
84
|
-
* Kendi sunucumuza yapılan istekler atlanır: ısıtma turu ve sağlık kontrolü
|
|
85
|
-
* upstream değil.
|
|
86
|
-
*
|
|
87
|
-
* @returns {void}
|
|
88
|
-
*/
|
|
89
|
-
export function trackUpstreamFetch() {
|
|
90
|
-
const original = globalThis.fetch;
|
|
91
|
-
if (/** @type {any} */ (original).__jskeletUpstreamTracked) return;
|
|
92
|
-
|
|
93
|
-
/** @type {typeof fetch} */
|
|
94
|
-
const wrapped = async (input, init) => {
|
|
95
|
-
// İstek bir render bağlamı içinde değilse (script, zamanlayıcı) hiçbir
|
|
96
|
-
// şey yapılmaz: sarmalayıcının maliyeti bir `getStore()` çağrısı.
|
|
97
|
-
if (!storage.getStore()) return original(input, init);
|
|
98
|
-
|
|
99
|
-
const url = requestUrl(input);
|
|
100
|
-
if (isSelfRequest(url)) return original(input, init);
|
|
101
|
-
|
|
102
|
-
// Hız freni burada, çünkü kotayı harcayan şey sayfa değil bu çağrı.
|
|
103
|
-
// Kapalıysa (varsayılan) `null` döner ve tek maliyeti bir dal.
|
|
104
|
-
const permit = await limitUpstream(url);
|
|
105
|
-
|
|
106
|
-
if (permit?.blocked) {
|
|
107
|
-
// Devre kesici açık: 429 yiyeceğini bildiğimiz bir çağrıyı yapmıyoruz.
|
|
108
|
-
// Render tarafı bunu geçici hata olarak görür, yani sayfa önbelleğe
|
|
109
|
-
// yazılmaz ve bir sonraki istek yeniden dener.
|
|
110
|
-
reportUpstreamFailure({ status: 429, path: url });
|
|
111
|
-
return new Response(null, { status: 429, statusText: "Too Many Requests" });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const response = await original(input, init);
|
|
116
|
-
|
|
117
|
-
if (permit) {
|
|
118
|
-
noteUpstreamResponse(permit.host, response.status, response.headers.get("retry-after"));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!response.ok && isTransientStatus(response.status)) {
|
|
122
|
-
reportUpstreamFailure({ status: response.status, path: url });
|
|
123
|
-
}
|
|
124
|
-
return response;
|
|
125
|
-
} catch (error) {
|
|
126
|
-
// Yanıt hiç gelmedi: ağ hatası her zaman geçicidir.
|
|
127
|
-
if (permit) noteUpstreamResponse(permit.host, 0, null);
|
|
128
|
-
reportUpstreamFailure({ status: 0, path: url });
|
|
129
|
-
throw error;
|
|
130
|
-
} finally {
|
|
131
|
-
permit?.release();
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/** @type {any} */ (wrapped).__jskeletUpstreamTracked = true;
|
|
136
|
-
globalThis.fetch = wrapped;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* @param {RequestInfo | URL} input
|
|
141
|
-
* @returns {string}
|
|
142
|
-
*/
|
|
143
|
-
function requestUrl(input) {
|
|
144
|
-
if (typeof input === "string") return input;
|
|
145
|
-
if (input instanceof URL) return input.href;
|
|
146
|
-
return /** @type {Request} */ (input)?.url ?? String(input);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** @param {string} url */
|
|
150
|
-
function isSelfRequest(url) {
|
|
151
|
-
return /^https?:\/\/(127\.0\.0\.1|\[::1\]|localhost)(:|\/|$)/i.test(url);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* @param {() => T} run
|
|
156
|
-
* @returns {T}
|
|
157
|
-
* @template T
|
|
158
|
-
*/
|
|
159
|
-
export function withUpstreamTracking(run) {
|
|
160
|
-
return storage.run({ failures: [] }, run);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** @returns {UpstreamFailure[]} */
|
|
164
|
-
export function getUpstreamFailures() {
|
|
165
|
-
return storage.getStore()?.failures ?? [];
|
|
166
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Render başına upstream API hatalarını toplar.
|
|
3
|
+
*
|
|
4
|
+
* `render.js` her sayfayı bu bağlam içinde üretir. Böylece HTML önbelleği "bu
|
|
5
|
+
* çıktı eksik veriyle üretildi" bilgisine sahip olur ve bozuk sayfayı saklamaz;
|
|
6
|
+
* `notFound()` de geçici bir hataya denk geldiğinde 404 olmaktan çıkar.
|
|
7
|
+
*
|
|
8
|
+
* Bilgi iki yoldan gelir:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Otomatik** — `trackUpstreamFetch()` `globalThis.fetch`i sarar ve
|
|
11
|
+
* geçici hataları (429, 5xx, ağ) kendiliğinden bildirir. `createApp()`
|
|
12
|
+
* bunu açılışta kurar, yani hiçbir uygulama kodu gerekmez.
|
|
13
|
+
* 2. **Elle** — `fetch` kullanmayan bir istemci (veritabanı sürücüsü, gRPC,
|
|
14
|
+
* SDK) için:
|
|
15
|
+
*
|
|
16
|
+
* import { reportUpstreamFailure } from "jskelet";
|
|
17
|
+
*
|
|
18
|
+
* if (!response.ok) {
|
|
19
|
+
* reportUpstreamFailure({ status: response.status, path: url });
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* İki yol aynı hatayı bildirirse tekilleştirilir.
|
|
23
|
+
*
|
|
24
|
+
* Sarmalayıcı aynı zamanda hız freninin durduğu yerdir
|
|
25
|
+
* (`upstream-limiter.js`): kotayı harcayan şey sayfa isteği değil, buradan
|
|
26
|
+
* geçen çağrı. Fren varsayılan olarak kapalı.
|
|
27
|
+
*/
|
|
28
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
29
|
+
import { limitUpstream, noteUpstreamResponse } from "./upstream-limiter.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {{ status: number, path: string }} UpstreamFailure
|
|
33
|
+
* `status: 0` ağ hatası anlamına gelir (yanıt hiç gelmedi).
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** @type {AsyncLocalStorage<{ failures: UpstreamFailure[] }>} */
|
|
37
|
+
const storage = new AsyncLocalStorage();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {UpstreamFailure} failure
|
|
41
|
+
* @returns {void}
|
|
42
|
+
*/
|
|
43
|
+
export function reportUpstreamFailure(failure) {
|
|
44
|
+
const store = storage.getStore();
|
|
45
|
+
if (!store) return;
|
|
46
|
+
|
|
47
|
+
// Aynı hatayı hem otomatik sarmalayıcı hem uygulamanın istemcisi
|
|
48
|
+
// bildirebilir; aynı satırı iki kez loglamanın faydası yok.
|
|
49
|
+
const duplicate = store.failures.some(
|
|
50
|
+
(existing) => existing.status === failure.status && existing.path === failure.path,
|
|
51
|
+
);
|
|
52
|
+
if (!duplicate) store.failures.push(failure);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Geçici sayılan durumlar: tekrar denemekle düzelebilenler. Bu liste
|
|
57
|
+
* `render.js` ile paylaşılır — hangi hatanın önbelleği engellediği ve hangi
|
|
58
|
+
* hatanın `notFound()`u 404 olmaktan çıkardığı tek yerde tanımlı olsun.
|
|
59
|
+
*/
|
|
60
|
+
const TRANSIENT_STATUSES = new Set([0, 408, 425, 429]);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {number} status
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
export function isTransientStatus(status) {
|
|
67
|
+
return TRANSIENT_STATUSES.has(status) || status >= 500;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* `globalThis.fetch`i sarıp **geçici** upstream hatalarını kendiliğinden
|
|
72
|
+
* bildirir.
|
|
73
|
+
*
|
|
74
|
+
* Gerekçesi pratik: `reportUpstreamFailure()` sözleşmesi uygulamanın HTTP
|
|
75
|
+
* istemcisine bir satır eklemeyi gerektiriyor ve o satır yazılmadığında
|
|
76
|
+
* framework rate limit'i hiç göremiyor — veri gelmediği için `notFound()`
|
|
77
|
+
* çağıran sayfa 404 olarak servis ediliyordu. Otomatik izleme bu bilgiyi
|
|
78
|
+
* varsayılan hâle getirir; elle çağrı hâlâ geçerli ve tekilleştirilir.
|
|
79
|
+
*
|
|
80
|
+
* Yalnızca geçici durumlar bildirilir. `404`/`403` gibi deterministik
|
|
81
|
+
* cevaplar birçok API'de "böyle bir kayıt yok" anlamına geliyor ve onları
|
|
82
|
+
* otomatik olarak "eksik veri" saymak her sayfada yanlış uyarı üretirdi.
|
|
83
|
+
*
|
|
84
|
+
* Kendi sunucumuza yapılan istekler atlanır: ısıtma turu ve sağlık kontrolü
|
|
85
|
+
* upstream değil.
|
|
86
|
+
*
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
export function trackUpstreamFetch() {
|
|
90
|
+
const original = globalThis.fetch;
|
|
91
|
+
if (/** @type {any} */ (original).__jskeletUpstreamTracked) return;
|
|
92
|
+
|
|
93
|
+
/** @type {typeof fetch} */
|
|
94
|
+
const wrapped = async (input, init) => {
|
|
95
|
+
// İstek bir render bağlamı içinde değilse (script, zamanlayıcı) hiçbir
|
|
96
|
+
// şey yapılmaz: sarmalayıcının maliyeti bir `getStore()` çağrısı.
|
|
97
|
+
if (!storage.getStore()) return original(input, init);
|
|
98
|
+
|
|
99
|
+
const url = requestUrl(input);
|
|
100
|
+
if (isSelfRequest(url)) return original(input, init);
|
|
101
|
+
|
|
102
|
+
// Hız freni burada, çünkü kotayı harcayan şey sayfa değil bu çağrı.
|
|
103
|
+
// Kapalıysa (varsayılan) `null` döner ve tek maliyeti bir dal.
|
|
104
|
+
const permit = await limitUpstream(url);
|
|
105
|
+
|
|
106
|
+
if (permit?.blocked) {
|
|
107
|
+
// Devre kesici açık: 429 yiyeceğini bildiğimiz bir çağrıyı yapmıyoruz.
|
|
108
|
+
// Render tarafı bunu geçici hata olarak görür, yani sayfa önbelleğe
|
|
109
|
+
// yazılmaz ve bir sonraki istek yeniden dener.
|
|
110
|
+
reportUpstreamFailure({ status: 429, path: url });
|
|
111
|
+
return new Response(null, { status: 429, statusText: "Too Many Requests" });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const response = await original(input, init);
|
|
116
|
+
|
|
117
|
+
if (permit) {
|
|
118
|
+
noteUpstreamResponse(permit.host, response.status, response.headers.get("retry-after"));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!response.ok && isTransientStatus(response.status)) {
|
|
122
|
+
reportUpstreamFailure({ status: response.status, path: url });
|
|
123
|
+
}
|
|
124
|
+
return response;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// Yanıt hiç gelmedi: ağ hatası her zaman geçicidir.
|
|
127
|
+
if (permit) noteUpstreamResponse(permit.host, 0, null);
|
|
128
|
+
reportUpstreamFailure({ status: 0, path: url });
|
|
129
|
+
throw error;
|
|
130
|
+
} finally {
|
|
131
|
+
permit?.release();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** @type {any} */ (wrapped).__jskeletUpstreamTracked = true;
|
|
136
|
+
globalThis.fetch = wrapped;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {RequestInfo | URL} input
|
|
141
|
+
* @returns {string}
|
|
142
|
+
*/
|
|
143
|
+
function requestUrl(input) {
|
|
144
|
+
if (typeof input === "string") return input;
|
|
145
|
+
if (input instanceof URL) return input.href;
|
|
146
|
+
return /** @type {Request} */ (input)?.url ?? String(input);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @param {string} url */
|
|
150
|
+
function isSelfRequest(url) {
|
|
151
|
+
return /^https?:\/\/(127\.0\.0\.1|\[::1\]|localhost)(:|\/|$)/i.test(url);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {() => T} run
|
|
156
|
+
* @returns {T}
|
|
157
|
+
* @template T
|
|
158
|
+
*/
|
|
159
|
+
export function withUpstreamTracking(run) {
|
|
160
|
+
return storage.run({ failures: [] }, run);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** @returns {UpstreamFailure[]} */
|
|
164
|
+
export function getUpstreamFailures() {
|
|
165
|
+
return storage.getStore()?.failures ?? [];
|
|
166
|
+
}
|
package/src/start.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prod sunucu girişi. Build çıktısı yoksa önce üretir, sonra dinlemeye başlar.
|
|
3
|
-
*/
|
|
4
|
-
import "./build/ensure-build.mjs";
|
|
5
|
-
import { startServer } from "./server/create-app.js";
|
|
6
|
-
|
|
7
|
-
await startServer();
|
|
1
|
+
/**
|
|
2
|
+
* Prod sunucu girişi. Build çıktısı yoksa önce üretir, sonra dinlemeye başlar.
|
|
3
|
+
*/
|
|
4
|
+
import "./build/ensure-build.mjs";
|
|
5
|
+
import { startServer } from "./server/create-app.js";
|
|
6
|
+
|
|
7
|
+
await startServer();
|
package/src/templates/layout.ejs
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
<%#
|
|
2
|
-
JSkelet varsayılan layout'u.
|
|
3
|
-
|
|
4
|
-
Uygulama `views/layout.ejs` oluşturduğu anda bu dosya devre dışı kalır;
|
|
5
|
-
amacı yeni bir projenin tek route ile çalışabilmesi. Kopyalayıp başlangıç
|
|
6
|
-
noktası olarak kullanın.
|
|
7
|
-
|
|
8
|
-
Kullanılabilir local'ler: metadata, headMeta, extraHead, structuredData,
|
|
9
|
-
body, bodyClass, entries, pathname, lang, devtools, devBasePath, asset,
|
|
10
|
-
hasAsset + tüm html/tag helper'ları ve views/components/** export'ları.
|
|
11
|
-
`hooks.layoutContext()` döndürdüğü her alan da buraya eklenir.
|
|
12
|
-
-%>
|
|
13
|
-
<!DOCTYPE html>
|
|
14
|
-
<html lang="<%= lang %>">
|
|
15
|
-
<head>
|
|
16
|
-
<meta charset="utf-8">
|
|
17
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
18
|
-
<%# Kaynak ipuçları en başta: preconnect ve LCP preload'ını geciktirmek
|
|
19
|
-
doğrudan LCP'ye yazılır. Sayfaya özel head buradan gelir. %>
|
|
20
|
-
<%- extraHead %>
|
|
21
|
-
<%# Tek, render-blocking stylesheet — build çalışmadıysa hiç basılmaz. %>
|
|
22
|
-
<% if (hasAsset('app.css')) { %>
|
|
23
|
-
<link rel="stylesheet" href="<%= asset('app.css') %>">
|
|
24
|
-
<% } %>
|
|
25
|
-
<%- headMeta %>
|
|
26
|
-
<% structuredData.forEach(function (item) { %>
|
|
27
|
-
<script type="application/ld+json"><%- jsonScript(item) %></script>
|
|
28
|
-
<% }); %>
|
|
29
|
-
</head>
|
|
30
|
-
<body class="<%= bodyClass %>">
|
|
31
|
-
<%- body %>
|
|
32
|
-
<%# Island runtime; entry yoksa sayfa yine tam çalışır (sadece JS'siz). %>
|
|
33
|
-
<% if (hasAsset('main.js')) { %>
|
|
34
|
-
<script type="module" src="<%= asset('main.js') %>"></script>
|
|
35
|
-
<% } %>
|
|
36
|
-
<% entries.forEach(function (entry) { %>
|
|
37
|
-
<script type="module" src="<%= asset(entry) %>"></script>
|
|
38
|
-
<% }); %>
|
|
39
|
-
<%# Dev overlay: prod build'de yok, yalnızca `jskelet dev` sunucusundan. %>
|
|
40
|
-
<% if (devtools) { %>
|
|
41
|
-
<script type="module" src="<%= devBasePath %>/overlay.js"></script>
|
|
42
|
-
<% } %>
|
|
43
|
-
</body>
|
|
44
|
-
</html>
|
|
1
|
+
<%#
|
|
2
|
+
JSkelet varsayılan layout'u.
|
|
3
|
+
|
|
4
|
+
Uygulama `views/layout.ejs` oluşturduğu anda bu dosya devre dışı kalır;
|
|
5
|
+
amacı yeni bir projenin tek route ile çalışabilmesi. Kopyalayıp başlangıç
|
|
6
|
+
noktası olarak kullanın.
|
|
7
|
+
|
|
8
|
+
Kullanılabilir local'ler: metadata, headMeta, extraHead, structuredData,
|
|
9
|
+
body, bodyClass, entries, pathname, lang, devtools, devBasePath, asset,
|
|
10
|
+
hasAsset + tüm html/tag helper'ları ve views/components/** export'ları.
|
|
11
|
+
`hooks.layoutContext()` döndürdüğü her alan da buraya eklenir.
|
|
12
|
+
-%>
|
|
13
|
+
<!DOCTYPE html>
|
|
14
|
+
<html lang="<%= lang %>">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
18
|
+
<%# Kaynak ipuçları en başta: preconnect ve LCP preload'ını geciktirmek
|
|
19
|
+
doğrudan LCP'ye yazılır. Sayfaya özel head buradan gelir. %>
|
|
20
|
+
<%- extraHead %>
|
|
21
|
+
<%# Tek, render-blocking stylesheet — build çalışmadıysa hiç basılmaz. %>
|
|
22
|
+
<% if (hasAsset('app.css')) { %>
|
|
23
|
+
<link rel="stylesheet" href="<%= asset('app.css') %>">
|
|
24
|
+
<% } %>
|
|
25
|
+
<%- headMeta %>
|
|
26
|
+
<% structuredData.forEach(function (item) { %>
|
|
27
|
+
<script type="application/ld+json"><%- jsonScript(item) %></script>
|
|
28
|
+
<% }); %>
|
|
29
|
+
</head>
|
|
30
|
+
<body class="<%= bodyClass %>">
|
|
31
|
+
<%- body %>
|
|
32
|
+
<%# Island runtime; entry yoksa sayfa yine tam çalışır (sadece JS'siz). %>
|
|
33
|
+
<% if (hasAsset('main.js')) { %>
|
|
34
|
+
<script type="module" src="<%= asset('main.js') %>"></script>
|
|
35
|
+
<% } %>
|
|
36
|
+
<% entries.forEach(function (entry) { %>
|
|
37
|
+
<script type="module" src="<%= asset(entry) %>"></script>
|
|
38
|
+
<% }); %>
|
|
39
|
+
<%# Dev overlay: prod build'de yok, yalnızca `jskelet dev` sunucusundan. %>
|
|
40
|
+
<% if (devtools) { %>
|
|
41
|
+
<script type="module" src="<%= devBasePath %>/overlay.js"></script>
|
|
42
|
+
<% } %>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
package/src/version.mjs
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Framework sürümü. `package.json`'dan okunur; paket bir kez yayınlandıktan
|
|
3
|
-
* sonra sürümü iki yerde tutmak kaçınılmaz olarak birbirinden ayrılıyor.
|
|
4
|
-
*/
|
|
5
|
-
import fs from "node:fs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { FRAMEWORK_ROOT } from "./config/index.js";
|
|
8
|
-
|
|
9
|
-
const manifest = (() => {
|
|
10
|
-
try {
|
|
11
|
-
const file = path.join(FRAMEWORK_ROOT, "package.json");
|
|
12
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
13
|
-
} catch {
|
|
14
|
-
return {};
|
|
15
|
-
}
|
|
16
|
-
})();
|
|
17
|
-
|
|
18
|
-
/** @type {string} */
|
|
19
|
-
export const FRAMEWORK_VERSION = manifest.version ?? "0.0.0";
|
|
20
|
-
|
|
21
|
-
/** Paket adı: sürüm kontrolü hangi kayıt defteri girdisine bakacağını buradan bilir. */
|
|
22
|
-
export const FRAMEWORK_PACKAGE = manifest.name ?? "jskelet";
|
|
23
|
-
|
|
24
|
-
/** Künye alanları: yönetim panelinin footer'ı bunları basıyor. */
|
|
25
|
-
export const FRAMEWORK_LICENSE = manifest.license ?? "MIT";
|
|
26
|
-
|
|
27
|
-
/** @type {string} */
|
|
28
|
-
export const FRAMEWORK_NODE_RANGE = manifest.engines?.node ?? "";
|
|
29
|
-
|
|
30
|
-
/** @type {string} */
|
|
31
|
-
export const FRAMEWORK_HOMEPAGE = manifest.homepage ?? "";
|
|
1
|
+
/**
|
|
2
|
+
* Framework sürümü. `package.json`'dan okunur; paket bir kez yayınlandıktan
|
|
3
|
+
* sonra sürümü iki yerde tutmak kaçınılmaz olarak birbirinden ayrılıyor.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { FRAMEWORK_ROOT } from "./config/index.js";
|
|
8
|
+
|
|
9
|
+
const manifest = (() => {
|
|
10
|
+
try {
|
|
11
|
+
const file = path.join(FRAMEWORK_ROOT, "package.json");
|
|
12
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
|
|
18
|
+
/** @type {string} */
|
|
19
|
+
export const FRAMEWORK_VERSION = manifest.version ?? "0.0.0";
|
|
20
|
+
|
|
21
|
+
/** Paket adı: sürüm kontrolü hangi kayıt defteri girdisine bakacağını buradan bilir. */
|
|
22
|
+
export const FRAMEWORK_PACKAGE = manifest.name ?? "jskelet";
|
|
23
|
+
|
|
24
|
+
/** Künye alanları: yönetim panelinin footer'ı bunları basıyor. */
|
|
25
|
+
export const FRAMEWORK_LICENSE = manifest.license ?? "MIT";
|
|
26
|
+
|
|
27
|
+
/** @type {string} */
|
|
28
|
+
export const FRAMEWORK_NODE_RANGE = manifest.engines?.node ?? "";
|
|
29
|
+
|
|
30
|
+
/** @type {string} */
|
|
31
|
+
export const FRAMEWORK_HOMEPAGE = manifest.homepage ?? "";
|