jskelet 0.1.1 → 0.1.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/AGENTS.md +5 -0
- package/CHANGELOG.md +63 -0
- package/README.md +21 -7
- package/bin/jskelet.mjs +6 -6
- package/docs/03-routing.md +48 -9
- package/docs/04-render-ve-sablonlar.md +2 -2
- package/docs/05-islands.md +59 -6
- package/docs/06-cache.md +39 -7
- package/docs/07-yapilandirma.md +51 -1
- package/docs/08-build.md +4 -4
- package/docs/09-dev-araclari.md +5 -0
- package/docs/12-panel-ve-oturum.md +384 -0
- package/docs/README.md +25 -2
- package/docs/en/01-getting-started.md +292 -0
- package/docs/en/02-architecture.md +305 -0
- package/docs/en/03-routing.md +493 -0
- package/docs/en/04-rendering.md +504 -0
- package/docs/en/05-islands.md +492 -0
- package/docs/en/06-caching.md +454 -0
- package/docs/en/07-configuration.md +736 -0
- package/docs/en/08-build.md +383 -0
- package/docs/en/09-dev-tools.md +314 -0
- package/docs/en/10-deployment.md +332 -0
- package/docs/en/11-migration.md +360 -0
- package/docs/en/12-dashboards-and-sessions.md +392 -0
- package/docs/en/README.md +112 -0
- package/package.json +4 -2
- package/src/build/build.mjs +1 -1
- package/src/build/tasks/client.mjs +2 -2
- package/src/build/tasks/fonts.mjs +3 -3
- package/src/build/tasks/icons.mjs +1 -1
- package/src/build/tasks/images.mjs +2 -2
- package/src/client/devtools/overlay.js +196 -164
- package/src/client/devtools/report.js +96 -96
- package/src/client/form.js +192 -0
- package/src/client/index.js +10 -1
- package/src/client/registry.js +78 -4
- package/src/client/swap.js +188 -0
- package/src/config/defaults.js +34 -0
- package/src/config/index.js +68 -13
- package/src/config/pattern.js +1 -1
- package/src/dev-server.mjs +1 -1
- package/src/http/control-flow.js +16 -1
- package/src/http/cookies.js +257 -0
- package/src/http/request-context.js +162 -0
- package/src/index.js +19 -2
- package/src/init.mjs +32 -31
- package/src/log.mjs +8 -2
- package/src/logo.png +0 -0
- package/src/runtime/alias-hooks.mjs +1 -1
- package/src/server/assets.js +1 -1
- package/src/server/create-app.js +12 -4
- package/src/server/dev/devtools.js +6 -2
- package/src/server/dev/version-check.mjs +139 -0
- package/src/server/head-hints.js +1 -1
- package/src/server/html-cache.js +10 -4
- package/src/server/middleware/csrf.js +134 -0
- package/src/server/prewarm.js +6 -6
- package/src/server/render.js +199 -16
- package/src/server/router.js +14 -7
- package/src/server/status-page.js +1 -1
- package/src/version.mjs +9 -4
- package/src/views/components/loader.js +1 -1
- package/src/views/helpers/tags.js +53 -1
package/AGENTS.md
CHANGED
|
@@ -24,6 +24,11 @@ zaten tartışılmış:
|
|
|
24
24
|
| `src/config/**` | [07-yapilandirma.md](./docs/07-yapilandirma.md) |
|
|
25
25
|
| `src/dev-server.mjs`, `src/server/dev/**` | [09-dev-araclari.md](./docs/09-dev-araclari.md) |
|
|
26
26
|
|
|
27
|
+
Belgeler iki dilde: Türkçesi `docs/`, İngilizcesi `docs/en/` altında ve
|
|
28
|
+
dosyalar birebir eşlenik. Bir belgeyi değiştirdiysen karşılığını da güncelle;
|
|
29
|
+
pazarlama sitesi (`examples/marketing`) bu dosyaları doğrudan `/docs` altında
|
|
30
|
+
servis ettiği için eksik kalan çeviri kullanıcıya görünür.
|
|
31
|
+
|
|
27
32
|
## Doğrulama
|
|
28
33
|
|
|
29
34
|
**Lint yeterli, tam build zorunlu değil.**
|
package/CHANGELOG.md
CHANGED
|
@@ -10,8 +10,71 @@ one is listed under a **Breaking** heading.
|
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
|
+
- `route(fn, { private: true })` for pages that depend on the visitor. The HTML
|
|
14
|
+
cache is bypassed, `cache.html` patterns can no longer turn caching on for
|
|
15
|
+
that route, and the response is sent with `private, no-store`, `Vary: Cookie`
|
|
16
|
+
and no ETag.
|
|
17
|
+
- A runtime guard against identity leaks: when a cacheable route reads
|
|
18
|
+
`Cookie`, `Authorization` or a session field, the rendered HTML is never
|
|
19
|
+
stored. In development the request fails with an explanation, in production it
|
|
20
|
+
is served with `no-store` and logged.
|
|
21
|
+
- `fragment()` for layout-less partial responses, with `no-store` and cache
|
|
22
|
+
bypass built in.
|
|
23
|
+
- CSRF protection. Cross-site state-changing requests are rejected based on
|
|
24
|
+
`Origin` and `Sec-Fetch-Site`; requests carrying neither header still pass, so
|
|
25
|
+
webhooks keep working. An optional double-submit token layer is enabled with
|
|
26
|
+
`security.csrf.token` and rendered into forms by the new `csrfField()` helper.
|
|
27
|
+
- Signed cookie helpers under `jskelet/cookies`: `parseCookies()`,
|
|
28
|
+
`setCookie()`, `clearCookie()`, `setSignedCookie()`, `getSignedCookie()`,
|
|
29
|
+
`randomToken()` and `safeEqual()`. Defaults are `HttpOnly`, `SameSite=Lax` and
|
|
30
|
+
`Secure` outside development.
|
|
31
|
+
- A `security` configuration section: `trustProxy`, `cookieSecret` and `csrf`.
|
|
32
|
+
- `seeOther()` for the post/redirect/get flow, which needs 303 rather than the
|
|
33
|
+
method-preserving 307 that `redirect()` sends.
|
|
34
|
+
- Island cleanup. A `mount()` function may return a teardown callback; it is now
|
|
35
|
+
stored and called by the new `unmount(root)` export when the subtree leaves
|
|
36
|
+
the DOM.
|
|
37
|
+
- Client helpers for partial updates: `swap()` and `startSwapLinks()` for
|
|
38
|
+
fetching and replacing a region, `enhanceForm()` and `startForms()` for
|
|
39
|
+
submitting forms without a full page load while keeping the no-JavaScript
|
|
40
|
+
path working.
|
|
41
|
+
- A fourth example, `examples/dashboard`: sign-in with a signed cookie session,
|
|
42
|
+
a private page, a paginated table fragment, a CSRF-protected mutation and an
|
|
43
|
+
island with cleanup, covered by its own `smoke.mjs`.
|
|
44
|
+
- An npm version badge in the `README`, linking to the package page.
|
|
13
45
|
- English `README`, plus `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`,
|
|
14
46
|
a `LICENSE` file, issue and pull request templates, and a CI workflow.
|
|
47
|
+
- An English edition of the documentation under `docs/en/`, mirroring every
|
|
48
|
+
chapter of the Turkish `docs/`.
|
|
49
|
+
- The dev overlay now compares the installed version against the `latest` tag on
|
|
50
|
+
npm: the Server tab shows the version, marks an `update` chip when a newer
|
|
51
|
+
release exists and offers the upgrade command. The lookup is cached for six
|
|
52
|
+
hours, never blocks the server and can be turned off with
|
|
53
|
+
`JSKELET_VERSION_CHECK=0`.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- `trust proxy` is now configurable through `security.trustProxy` instead of
|
|
58
|
+
being always on. The default is unchanged, but a server exposed directly to
|
|
59
|
+
the internet should turn it off: while it is on, a client can forge its own
|
|
60
|
+
`X-Forwarded-For` and rate limiting or audit logs see the wrong address.
|
|
61
|
+
- Every message the framework prints is now English: config, router, render,
|
|
62
|
+
cache, prewarm, asset and build warnings, CLI output, the project `jskelet
|
|
63
|
+
init` scaffolds, and the devtools overlay and report interfaces. Visitor-facing
|
|
64
|
+
status pages still follow `brand.lang` and keep their Turkish translations.
|
|
65
|
+
- The dev overlay and report now show the current JSkelet logo, served with a
|
|
66
|
+
cacheable response instead of being re-fetched on every navigation.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- A page rendered without `revalidate` used to be sent with no `Cache-Control`
|
|
71
|
+
at all, while still carrying a strong ETag. HTTP treats such a response as
|
|
72
|
+
heuristically cacheable, so an intermediate proxy or the browser's back button
|
|
73
|
+
could store a response meant for a single visitor. Dynamic pages now send
|
|
74
|
+
`private, no-store` and no ETag.
|
|
75
|
+
- A redirect thrown from a route that reads the session is no longer cacheable
|
|
76
|
+
either; a stored "you need to sign in" redirect used to follow the visitor
|
|
77
|
+
even after signing in.
|
|
15
78
|
|
|
16
79
|
## [0.1.0]
|
|
17
80
|
|
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Tailwind v4 stylesheet**, and instead of ISR keeps an in-process **HTML TTL
|
|
|
9
9
|
cache** with stale-while-revalidate. No React, no TypeScript — plain JavaScript
|
|
10
10
|
with JSDoc.
|
|
11
11
|
|
|
12
|
+
[](https://www.npmjs.com/package/jskelet)
|
|
12
13
|
[](https://nodejs.org)
|
|
13
14
|
[](./LICENSE)
|
|
14
15
|
|
|
@@ -171,10 +172,15 @@ client.
|
|
|
171
172
|
everything".
|
|
172
173
|
- **No global state management** beyond the small island store.
|
|
173
174
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
An app-shaped interface behind a login — a dashboard, an editor, an admin panel
|
|
176
|
+
— cannot benefit from the HTML cache, which is the main reason to pick this
|
|
177
|
+
framework. It is supported rather than recommended: `route(fn, { private: true })`
|
|
178
|
+
keeps per-visitor pages out of the cache, and signed cookies, CSRF, fragment
|
|
179
|
+
endpoints and region swapping cover the rest
|
|
180
|
+
([docs/12-panel-ve-oturum.md](./docs/12-panel-ve-oturum.md)). Live data
|
|
181
|
+
transport is deliberately left to you; pick SSE, WebSocket or polling yourself.
|
|
182
|
+
A feature-by-feature comparison with Next.js is in
|
|
183
|
+
[docs/11-tasima.md](./docs/11-tasima.md).
|
|
178
184
|
|
|
179
185
|
## Project layout
|
|
180
186
|
|
|
@@ -260,10 +266,11 @@ Only the specifiers in the `exports` map are supported:
|
|
|
260
266
|
|
|
261
267
|
| Specifier | Contents |
|
|
262
268
|
| --- | --- |
|
|
263
|
-
| `jskelet` | `route`, `createApp`, `startServer`, `notFound`, `redirect`, `cache`, `asset`, `getConfig`, HTML cache and prewarm helpers |
|
|
264
|
-
| `jskelet/client` | `register`, `registerAll`, `hydrate`, `start`, `createStore`, DOM helpers |
|
|
269
|
+
| `jskelet` | `route`, `fragment`, `createApp`, `startServer`, `notFound`, `redirect`, `seeOther`, `cache`, `asset`, `getConfig`, cookie helpers, HTML cache and prewarm helpers |
|
|
270
|
+
| `jskelet/client` | `register`, `registerAll`, `hydrate`, `unmount`, `start`, `swap`, `startForms`, `createStore`, DOM helpers |
|
|
265
271
|
| `jskelet/html` | `attrs`, `cn`, `cx`, `esc`, `jsonScript` |
|
|
266
|
-
| `jskelet/tags` | `icon`, `image`, `link`, `preloadImage` |
|
|
272
|
+
| `jskelet/tags` | `icon`, `image`, `link`, `preloadImage`, `csrfField` |
|
|
273
|
+
| `jskelet/cookies` | `parseCookies`, `setCookie`, `clearCookie`, `setSignedCookie`, `getSignedCookie`, `randomToken`, `safeEqual` |
|
|
267
274
|
|
|
268
275
|
Anything reachable by a deeper path is internal and may change without notice.
|
|
269
276
|
|
|
@@ -295,6 +302,7 @@ written in Turkish; translations are a welcome contribution.
|
|
|
295
302
|
| [09-dev-araclari](./docs/09-dev-araclari.md) | Dev workflow, overlay, report page, dev gate |
|
|
296
303
|
| [10-dagitim](./docs/10-dagitim.md) | Production, Docker, reverse proxy, health checks |
|
|
297
304
|
| [11-tasima](./docs/11-tasima.md) | Migrating from Next.js: mapping table and plan |
|
|
305
|
+
| [12-panel-ve-oturum](./docs/12-panel-ve-oturum.md) | Per-visitor pages: `private: true`, sessions, CSRF, fragments, swapping |
|
|
298
306
|
|
|
299
307
|
If you work with AI agents, [AGENTS.md](./AGENTS.md) summarizes the rules that
|
|
300
308
|
apply to this repository.
|
|
@@ -305,6 +313,7 @@ apply to this repository.
|
|
|
305
313
|
npm --prefix examples/minimal install && npm --prefix examples/minimal run dev
|
|
306
314
|
npm --prefix examples/blog install && npm --prefix examples/blog run dev
|
|
307
315
|
npm --prefix examples/marketing install && npm --prefix examples/marketing run dev
|
|
316
|
+
npm --prefix examples/dashboard install && npm --prefix examples/dashboard run dev
|
|
308
317
|
```
|
|
309
318
|
|
|
310
319
|
- **`examples/minimal`** — two routes, one component, one island. The smallest
|
|
@@ -319,6 +328,11 @@ npm --prefix examples/marketing install && npm --prefix examples/marketing run d
|
|
|
319
328
|
are measured in the browser; there are no invented benchmarks. It is also
|
|
320
329
|
bilingual — English at the root, Turkish under `/tr` — which shows how to build
|
|
321
330
|
a multi-language site on a framework that ships no i18n of its own.
|
|
331
|
+
- **`examples/dashboard`** — the opposite axis: per-visitor pages. A signed
|
|
332
|
+
cookie session, a `private: true` page that never enters the HTML cache, a
|
|
333
|
+
paginated table fragment, a CSRF-protected form that still works without
|
|
334
|
+
JavaScript, and an island with cleanup. A public landing page sits next to it,
|
|
335
|
+
so a cached response and a `no-store` one are visible side by side.
|
|
322
336
|
|
|
323
337
|
With a server running, `node smoke.mjs` inside an example verifies that its
|
|
324
338
|
endpoints respond as expected.
|
package/bin/jskelet.mjs
CHANGED
|
@@ -91,13 +91,13 @@ switch (command) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
default: {
|
|
94
|
-
const known = command ? `
|
|
94
|
+
const known = command ? `unknown command: ${command}\n\n` : "";
|
|
95
95
|
process.stderr.write(
|
|
96
|
-
`${known}
|
|
97
|
-
" dev build watch +
|
|
98
|
-
" build
|
|
99
|
-
" start
|
|
100
|
-
" init
|
|
96
|
+
`${known}usage: jskelet <dev|build|start|init>\n\n` +
|
|
97
|
+
" dev build watch + server (live reload, dev overlay)\n" +
|
|
98
|
+
" build production build\n" +
|
|
99
|
+
" start production server\n" +
|
|
100
|
+
" init scaffold a minimal skeleton in the current directory\n",
|
|
101
101
|
);
|
|
102
102
|
process.exit(command ? 1 : 0);
|
|
103
103
|
}
|
package/docs/03-routing.md
CHANGED
|
@@ -28,11 +28,13 @@ gerekmez:
|
|
|
28
28
|
| Alan | Karşılığı |
|
|
29
29
|
| --- | --- |
|
|
30
30
|
| `route` | `jskelet` → `route` |
|
|
31
|
+
| `fragment` | `jskelet` → `fragment` |
|
|
31
32
|
| `renderView` | `jskelet` → `renderView` |
|
|
32
33
|
| `renderPage` | `jskelet` → `renderPage` |
|
|
33
34
|
| `notFound` | `jskelet` → `notFound` |
|
|
34
35
|
| `redirect` | `jskelet` → `redirect` |
|
|
35
36
|
| `permanentRedirect` | `jskelet` → `permanentRedirect` |
|
|
37
|
+
| `seeOther` | `jskelet` → `seeOther` |
|
|
36
38
|
|
|
37
39
|
İstersen doğrudan import da edebilirsin; `api` yalnızca kolaylık:
|
|
38
40
|
|
|
@@ -45,7 +47,7 @@ export function register(app) {
|
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
Modül geçerli bir fonksiyon açmazsa uyarı basılır ve atlanır:
|
|
48
|
-
`[router] <
|
|
50
|
+
`[router] <file> exports neither a default nor a 'register' function, skipped`.
|
|
49
51
|
|
|
50
52
|
## Yükleme sırası
|
|
51
53
|
|
|
@@ -97,8 +99,8 @@ Hiç route modülü bulunamazsa uyarı basılır ve sunucu yalnızca statik dosy
|
|
|
97
99
|
- `ctx` nesnesini kurar ve controller'ı çağırır.
|
|
98
100
|
- HTML TTL cache'ini uygular (`revalidate` varsa ve metot `GET` ise).
|
|
99
101
|
- `notFound()` / `redirect()` kontrol akışını yakalar.
|
|
100
|
-
- Yanıt başlıklarını yazar: `Content-Type
|
|
101
|
-
`Cache-Control
|
|
102
|
+
- Yanıt başlıklarını yazar: `Content-Type` ve cache durumuna göre
|
|
103
|
+
`Cache-Control` (+ önbelleklenebilir yanıtlarda `X-JSkelet-Cache`).
|
|
102
104
|
- Önbellekte saklanan sıkıştırılmış gövdeyi kullanarak yanıtı gönderir.
|
|
103
105
|
|
|
104
106
|
```js
|
|
@@ -114,11 +116,42 @@ app.get(
|
|
|
114
116
|
);
|
|
115
117
|
```
|
|
116
118
|
|
|
117
|
-
`options`
|
|
119
|
+
`options` iki alan kabul eder:
|
|
118
120
|
|
|
119
121
|
| Alan | Tip | Anlamı |
|
|
120
122
|
| --- | --- | --- |
|
|
121
123
|
| `revalidate` | `number` (saniye) | HTML önbellek TTL'i. Verilmezse ya da 0 ise bu route önbelleklenmez. `jskelet.config.mjs` → `cache().html` içindeki eşleşen bir kural bu değeri **ezer**. |
|
|
124
|
+
| `private` | `boolean` | Sayfa ziyaretçiye bağlı. Önbellek devre dışı kalır, `cache().html` deseni bunu **ezemez**, yanıt `private, no-store` ve `Vary: Cookie` ile ETag'siz gider. |
|
|
125
|
+
|
|
126
|
+
Oturuma bağlı her sayfa `private: true` almalı; önbellek anahtarında kimlik
|
|
127
|
+
olmadığı için bayrak olmadan bir kullanıcının HTML'i bir başkasına servis
|
|
128
|
+
edilir. Framework bu hatayı çalışma zamanında da yakalıyor (controller cookie
|
|
129
|
+
okuduğunda render önbelleğe yazılmaz), ama doğru yer bayrak. Ayrıntılar
|
|
130
|
+
[12-panel-ve-oturum.md](./12-panel-ve-oturum.md)'de.
|
|
131
|
+
|
|
132
|
+
## `fragment()` — layout'suz parça
|
|
133
|
+
|
|
134
|
+
Bir bölgeyi tazeleyen uçlar için. Layout basılmaz, yanıt `private, no-store` ve
|
|
135
|
+
ETag'siz gider, HTML önbelleğine hiç uğramaz.
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
app.get(
|
|
139
|
+
"/_fragment/satirlar",
|
|
140
|
+
fragment(async ({ query }) => ({
|
|
141
|
+
view: "partials/rows",
|
|
142
|
+
data: { rows: getRows(Number(query.sayfa ?? 1)) },
|
|
143
|
+
})),
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Controller `{ view, data?, status? }` ya da doğrudan bir HTML string döner.
|
|
148
|
+
Hata durumunda tüm sayfa yerine küçük bir uyarı parçası döner
|
|
149
|
+
(`<div role="alert" data-fragment-error>`), çünkü takas edilen bölge bir hata
|
|
150
|
+
sayfasının tamamını içine almamalı.
|
|
151
|
+
|
|
152
|
+
`fragment()` POST için de kullanılabilir: form gönderiminin cevabı olarak
|
|
153
|
+
güncellenmiş parçayı döndürmenin yolu bu, ve şablonda `csrfField()`
|
|
154
|
+
çalışabilmesi için gereken istek bağlamını da kuruyor.
|
|
122
155
|
|
|
123
156
|
## `ctx` — controller bağlamı
|
|
124
157
|
|
|
@@ -200,18 +233,24 @@ fonksiyon `throw` eder, framework yakalar. Böylece veri katmanındaki bir
|
|
|
200
233
|
fonksiyon, controller'a dönüş değeri taşımak zorunda kalmadan 404 üretebilir.
|
|
201
234
|
|
|
202
235
|
```js
|
|
203
|
-
import { notFound, redirect, permanentRedirect } from "jskelet";
|
|
236
|
+
import { notFound, redirect, permanentRedirect, seeOther } from "jskelet";
|
|
204
237
|
|
|
205
238
|
notFound(); // 404 → hooks.notFound() sayfası
|
|
206
|
-
redirect("/yeni-adres"); // 307 (geçici)
|
|
207
|
-
permanentRedirect("/yeni"); // 308 (
|
|
239
|
+
redirect("/yeni-adres"); // 307 (geçici, metodu korur)
|
|
240
|
+
permanentRedirect("/yeni"); // 308 (kalıcı, metodu korur)
|
|
241
|
+
seeOther("/panel"); // 303 (POST sonrası)
|
|
208
242
|
```
|
|
209
243
|
|
|
210
|
-
|
|
244
|
+
Dördü de `never` döner (her zaman fırlatır). Ayrıntı:
|
|
211
245
|
|
|
212
246
|
- `notFound()` → `NotFoundError` (`statusCode: 404`)
|
|
213
247
|
- `redirect(location)` → `RedirectError` (`statusCode: 307`)
|
|
214
248
|
- `permanentRedirect(location)` → `RedirectError` (`statusCode: 308`)
|
|
249
|
+
- `seeOther(location)` → `RedirectError` (`statusCode: 303`)
|
|
250
|
+
|
|
251
|
+
Bir POST handler'ında `redirect()` değil `seeOther()` kullanılır: 307 metodu
|
|
252
|
+
koruyor, yani tarayıcı hedefe yeniden POST ediyor. "Post/redirect/get" akışı —
|
|
253
|
+
geri tuşunun formu yeniden göndermediği akış — 303 gerektiriyor.
|
|
215
254
|
|
|
216
255
|
Özel bir durum kodu gerekiyorsa sınıfı doğrudan kullanabilirsin:
|
|
217
256
|
|
|
@@ -424,7 +463,7 @@ Yakalanan değerler `destination` içindeki aynı adlı `:param`'lara yazılır.
|
|
|
424
463
|
Parametre adı `[A-Za-z_][A-Za-z0-9_]*` kalıbına uymalıdır.
|
|
425
464
|
|
|
426
465
|
`source` mutlaka `/` ile başlamalı; başlamazsa kural yok sayılır ve uyarı
|
|
427
|
-
basılır (
|
|
466
|
+
basılır (``[config] invalid source (must start with `/`): …``). Tanınmayan bir
|
|
428
467
|
sözdizimi sessizce literal kabul edilmez.
|
|
429
468
|
|
|
430
469
|
Tam desen listesi ve config referansı: [07-yapilandirma.md](./07-yapilandirma.md).
|
|
@@ -194,8 +194,8 @@ Kurallar:
|
|
|
194
194
|
önce yüklenir. Tek amacı `lib/` yeniden ihraçlarını şablon local'i yapmak;
|
|
195
195
|
bileşenlerin kendi dosyaları sonradan gelip sessizce üzerine yazar.
|
|
196
196
|
- Aynı ad iki farklı bileşen dosyasında tanımlıysa uyarı basılır ve **ikincisi
|
|
197
|
-
kazanır**: `[components] 'card'
|
|
198
|
-
|
|
197
|
+
kazanır**: `[components] 'card' is defined twice: a.js and b.js — the second
|
|
198
|
+
one wins.`
|
|
199
199
|
- `views/components` dizini yoksa bileşen kaydı boş kalır; bileşen kullanmayan
|
|
200
200
|
bir proje de çalışır.
|
|
201
201
|
|
package/docs/05-islands.md
CHANGED
|
@@ -205,14 +205,67 @@ start();
|
|
|
205
205
|
- Bir element aynı island adıyla **iki kez bağlanmaz**; kayıt element bazında
|
|
206
206
|
`WeakMap` içinde tutulur.
|
|
207
207
|
- Kayıtlı olmayan bir ad için konsola uyarı basılır:
|
|
208
|
-
`[island]
|
|
208
|
+
`[island] not registered: <name>`.
|
|
209
209
|
- Modül import'u ya da `mount()` hata verirse konsola hata basılır
|
|
210
|
-
(`[island] <
|
|
210
|
+
(`[island] <name> failed to load`) ve **sayfanın kalanı etkilenmez**.
|
|
211
211
|
- `mount()` başarıyla dönerse elemente `data-island-ready="true"` yazılır.
|
|
212
|
-
- `mount()` bir
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
- `mount()` bir temizlik fonksiyonu döndürebilir; framework onu saklar ve
|
|
213
|
+
`unmount()` çağrıldığında işletir (aşağıya bakın).
|
|
214
|
+
|
|
215
|
+
### `unmount(root?)`
|
|
216
|
+
|
|
217
|
+
`root` altındaki island'ları söker: saklanan temizlik fonksiyonlarını çağırır,
|
|
218
|
+
`data-island-ready` işaretini kaldırır ve kaydı siler, böylece aynı düğüm
|
|
219
|
+
tekrar DOM'a girerse yeniden bağlanabilir. `root`'un kendisi de island olabilir.
|
|
220
|
+
|
|
221
|
+
DOM'un bir bölgesini değiştirirken çağrılması **zorunlu**:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
import { hydrate, unmount } from "jskelet/client";
|
|
225
|
+
|
|
226
|
+
unmount(container);
|
|
227
|
+
container.innerHTML = html;
|
|
228
|
+
hydrate(container);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Atlanması en kolay gözden kaçan sızıntı biçimini üretiyor. `innerHTML` ile
|
|
232
|
+
değiştirilen bir bölgenin island'ları DOM'dan çıkar, ama `document`/`window`
|
|
233
|
+
üzerine kurdukları dinleyiciler ve `setInterval`'ları yaşamaya devam eder;
|
|
234
|
+
birkaç takastan sonra aynı iş onlarca kez çalışır.
|
|
235
|
+
|
|
236
|
+
```js
|
|
237
|
+
export function mount(element) {
|
|
238
|
+
const timer = setInterval(() => tick(element), 1000);
|
|
239
|
+
const onResize = () => layout(element);
|
|
240
|
+
window.addEventListener("resize", onResize);
|
|
241
|
+
|
|
242
|
+
return () => {
|
|
243
|
+
clearInterval(timer);
|
|
244
|
+
window.removeEventListener("resize", onResize);
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`swap()` ve form yardımcıları `unmount()`u kendileri çağırıyor; elle DOM
|
|
250
|
+
değiştirdiğiniz yerlerde siz çağırıyorsunuz.
|
|
251
|
+
|
|
252
|
+
### `swap(target, url, options?)` ve `startSwapLinks(root?)`
|
|
253
|
+
|
|
254
|
+
Bir bölgeyi sunucudan gelen parçayla değiştirir: eski alt ağacı söker, içeriği
|
|
255
|
+
yazar, yeniden hidre eder ve odağı kaybolmuşsa geri getirir.
|
|
256
|
+
|
|
257
|
+
```html
|
|
258
|
+
<a href="/_fragment/satirlar?sayfa=2" data-swap="#satirlar">Sonraki</a>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Sunucu tarafı ve tüm seçenekler
|
|
262
|
+
[12-panel-ve-oturum.md](./12-panel-ve-oturum.md)'de.
|
|
263
|
+
|
|
264
|
+
### `enhanceForm(form)` ve `startForms(root?)`
|
|
265
|
+
|
|
266
|
+
`data-enhance` taşıyan formları sayfa yenilemeden gönderir; JS kapalıyken
|
|
267
|
+
normal POST + yönlendirme akışı çalışmaya devam eder. Sözleşmenin tamamı
|
|
268
|
+
[12-panel-ve-oturum.md](./12-panel-ve-oturum.md)'de.
|
|
216
269
|
|
|
217
270
|
## Durum paylaşımı: `createStore`
|
|
218
271
|
|
package/docs/06-cache.md
CHANGED
|
@@ -23,6 +23,29 @@ Sıra önemli: **istek içi cache en içte** olmalı ki aynı render'daki iki ç
|
|
|
23
23
|
tek upstream isteğine düşsün; **upstream takibi HTML cache'in içinde** olmalı ki
|
|
24
24
|
eksik veriyle üretilen çıktı önbelleğe yazılmasın.
|
|
25
25
|
|
|
26
|
+
## Public ve kişiye özel ayrımı
|
|
27
|
+
|
|
28
|
+
Bu belgedeki her şey **herkese aynı gidebilen** HTML için geçerli. Cache
|
|
29
|
+
anahtarında kimlik yok (yalnızca yol + query), yani önbellekteki bir sayfa onu
|
|
30
|
+
ilk isteyen kişinin değil, o yolun cevabıdır.
|
|
31
|
+
|
|
32
|
+
Kullanıcıya bağlı bir sayfa bu yüzden ayrı bir yoldan geçer:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
app.get("/panel", route(async ({ req }) => { … }, { private: true }));
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`private: true` üç şeyi birden yapar: önbellek devre dışı kalır, config'in
|
|
39
|
+
`cache.html` deseni bu kararı **ezemez** ve yanıt `private, no-store`,
|
|
40
|
+
`Vary: Cookie` ile, ETag'siz gider. Ayrıntılar ve oturum/CSRF tarafı
|
|
41
|
+
[12-panel-ve-oturum.md](./12-panel-ve-oturum.md)'de.
|
|
42
|
+
|
|
43
|
+
Bayrağı unutursanız framework sessiz kalmaz: controller `Cookie`,
|
|
44
|
+
`Authorization` ya da `req.session`/`req.user` okuduğu anda render işaretlenir
|
|
45
|
+
ve önbelleğe **yazılmaz**. Dev'de istek bir hatayla düşer, üretimde `no-store`
|
|
46
|
+
ile servis edilip loglanır. Koruma bir mazeret değil son savunma — doğru yer
|
|
47
|
+
`private: true`.
|
|
48
|
+
|
|
26
49
|
## `revalidate` — TTL nereden gelir
|
|
27
50
|
|
|
28
51
|
Bir route'un TTL'i iki kaynaktan gelebilir ve **config kazanır**:
|
|
@@ -31,6 +54,9 @@ Bir route'un TTL'i iki kaynaktan gelebilir ve **config kazanır**:
|
|
|
31
54
|
2. `jskelet.config.mjs` → `cache().html` içindeki eşleşen desen. Varsa
|
|
32
55
|
route'unkini ezer.
|
|
33
56
|
|
|
57
|
+
Tek istisna `private: true`: desen eşleşse bile yok sayılır. Kilit tek yönlü,
|
|
58
|
+
çünkü ters yönde bir hata sessiz veri sızıntısı anlamına geliyor.
|
|
59
|
+
|
|
34
60
|
```js
|
|
35
61
|
// jskelet.config.mjs
|
|
36
62
|
export default {
|
|
@@ -53,8 +79,14 @@ mümkün kılar; route dosyalarını dolaşmak gerekmez.
|
|
|
53
79
|
yapılmaz. Hiç `cache().html` kuralı yoksa doğrudan route'un değeri kullanılır.
|
|
54
80
|
|
|
55
81
|
`revalidate` verilmemişse ya da 0 ise sayfa **hiç önbelleklenmez**: her istek
|
|
56
|
-
render edilir ve
|
|
57
|
-
`X-JSkelet-Cache
|
|
82
|
+
render edilir ve yanıt `Cache-Control: private, no-store` ile, ETag'siz gider.
|
|
83
|
+
`X-JSkelet-Cache` başlığı da yazılmaz — önbellek yolu hiç çalışmadı, `MISS`
|
|
84
|
+
demek yanıltıcı olurdu.
|
|
85
|
+
|
|
86
|
+
Dinamik bir sayfaya `no-store` yazılması bilinçli. Hiç direktif taşımayan bir
|
|
87
|
+
yanıtı HTTP "sezgisel olarak önbelleklenebilir" sayıyor; araya giren bir proxy
|
|
88
|
+
ya da tarayıcının geri tuşu, tek bir ziyaretçi için üretilmiş HTML'i
|
|
89
|
+
saklayabiliyordu.
|
|
58
90
|
|
|
59
91
|
Önbellek ayrıca yalnızca `GET` istekleri için devreye girer.
|
|
60
92
|
|
|
@@ -92,7 +124,7 @@ Okuma davranışı:
|
|
|
92
124
|
|
|
93
125
|
Stale penceresinde tazelemenin hatası isteği etkilemez: eski HTML pencere
|
|
94
126
|
boyunca geçerli kalır ve hata yalnızca loglanır
|
|
95
|
-
(`[html-cache]
|
|
127
|
+
(`[html-cache] background refresh failed: …`).
|
|
96
128
|
|
|
97
129
|
Aynı anahtar için eşzamanlı tazelemeler tek bir çalışmada birleştirilir
|
|
98
130
|
(`inflight` haritası): yüz eşzamanlı istek tek render'a düşer.
|
|
@@ -220,8 +252,8 @@ export async function apiGet(path) {
|
|
|
220
252
|
|
|
221
253
|
| Durum | Sayılır | Sonuç |
|
|
222
254
|
| --- | --- | --- |
|
|
223
|
-
| `0` (ağ hatası), `408`, `425`, `429`, `>= 500` | **Geçici** | Sayfa önbelleğe yazılmaz, uyarı: `[render] <
|
|
224
|
-
| Diğerleri (`400`, `403`, `404`, …) | **Kalıcı** | Yalnızca uyarı: `[render] <
|
|
255
|
+
| `0` (ağ hatası), `408`, `425`, `429`, `>= 500` | **Geçici** | Sayfa önbelleğe yazılmaz, uyarı: `[render] <path> was produced with missing data, not caching it (…)` |
|
|
256
|
+
| Diğerleri (`400`, `403`, `404`, …) | **Kalıcı** | Yalnızca uyarı: `[render] <path> was produced with missing data, upstream is failing permanently (…)`. Önbellek engellenmez. |
|
|
225
257
|
|
|
226
258
|
Kalıcı hataların önbelleği engellememesi bilinçli: deterministik cevaplar tekrar
|
|
227
259
|
denemekle düzelmez. Onlar yüzünden önbelleği kapatmak sayfayı her ziyarette
|
|
@@ -319,7 +351,7 @@ Kurallar:
|
|
|
319
351
|
anda çekerken API'yi zorluyor. Tekrar turu bu sayfaların önbelleğe girmesini
|
|
320
352
|
sağlıyor; aksi hâlde ziyaretçi soğuk render'ı öder.
|
|
321
353
|
4. Özet loglanır:
|
|
322
|
-
`[prewarm] 128/130
|
|
354
|
+
`[prewarm] warmed 128/130 pages, 2 failed, 5 recovered on the retry pass (12.4s)`
|
|
323
355
|
|
|
324
356
|
İstekler `user-agent: jskelet-prewarm` (`brand.prewarmUserAgent`) ve
|
|
325
357
|
`accept-encoding: br, gzip` başlıklarıyla gider; ikincisi sıkıştırılmış gövdenin
|
|
@@ -392,7 +424,7 @@ turunun gerçekten `MISS` → önbellek doldurup doldurmadığını buradan gör
|
|
|
392
424
|
`cache().html` içindeki desen 0 saniye veriyor. Veya sayfa `status: 200`
|
|
393
425
|
dışında bir kod dönüyor.
|
|
394
426
|
- **Sayfa `MISS` dönüyor ama upstream sağlam.** Geçici bir upstream hatası
|
|
395
|
-
bildirilmiş olabilir; logda `
|
|
427
|
+
bildirilmiş olabilir; logda `was produced with missing data, not caching it`
|
|
396
428
|
satırını arayın.
|
|
397
429
|
- **Sürekli eski veri.** `revalidate` çok yüksek; unutmayın ki gerçek gecikme en
|
|
398
430
|
fazla `revalidate` + bir tazeleme turudur.
|
package/docs/07-yapilandirma.md
CHANGED
|
@@ -25,7 +25,7 @@ düz değer** olabilir; fonksiyon olmaları hâlinde `async` olabilirler ve `thi
|
|
|
25
25
|
config nesnesine bağlıdır. Bir bölüm hata verirse yalnızca o bölüm yok sayılır.
|
|
26
26
|
|
|
27
27
|
Config başarıyla yüklendiğinde bir özet basılır:
|
|
28
|
-
`[config] jskelet.config.mjs
|
|
28
|
+
`[config] jskelet.config.mjs loaded — 3 headers, 2 redirects, 1 cache rule`
|
|
29
29
|
|
|
30
30
|
## Tam örnek
|
|
31
31
|
|
|
@@ -62,6 +62,20 @@ export default {
|
|
|
62
62
|
devGateBypass: ["/api/healthcheck", "/robots.txt"],
|
|
63
63
|
preconnect: ["https://cdn.ornek.com"],
|
|
64
64
|
|
|
65
|
+
security: {
|
|
66
|
+
trustProxy: true,
|
|
67
|
+
cookieSecret: process.env.JSKELET_SECRET,
|
|
68
|
+
csrf: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
token: false,
|
|
71
|
+
allowedOrigins: [],
|
|
72
|
+
exclude: ["/webhook/:path*"],
|
|
73
|
+
cookieName: "csrf_token",
|
|
74
|
+
fieldName: "_csrf",
|
|
75
|
+
headerName: "x-csrf-token",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
|
|
65
79
|
navigation: {
|
|
66
80
|
prefetch: "moderate",
|
|
67
81
|
prerender: "conservative",
|
|
@@ -239,6 +253,37 @@ bir yapılandırmadır.
|
|
|
239
253
|
preconnect: ["https://cdn.ornek.com", "https://api.ornek.com"]
|
|
240
254
|
```
|
|
241
255
|
|
|
256
|
+
## `security`
|
|
257
|
+
|
|
258
|
+
**Tip:** `object` — **Varsayılan:**
|
|
259
|
+
`{ trustProxy: true, cookieSecret: null, csrf: { enabled: true, token: false, … } }`
|
|
260
|
+
|
|
261
|
+
Kişiye özel sayfaların tamamı ve gerekçeleri
|
|
262
|
+
[12-panel-ve-oturum.md](./12-panel-ve-oturum.md)'de; burada alanların referansı
|
|
263
|
+
var.
|
|
264
|
+
|
|
265
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
266
|
+
| --- | --- | --- | --- |
|
|
267
|
+
| `trustProxy` | `boolean` | `true` | Express'in `trust proxy` ayarı. Ters proxy arkasında doğru protokol ve istemci IP'si için gerekli. |
|
|
268
|
+
| `cookieSecret` | `string \| null` | `null` | İmzalı cookie sırrı. Verilmezse `JSKELET_SECRET` okunur. |
|
|
269
|
+
| `csrf.enabled` | `boolean` | `true` | Origin/`Sec-Fetch-Site` kontrolü. |
|
|
270
|
+
| `csrf.token` | `boolean` | `false` | Çift gönderim token'ı katmanı. |
|
|
271
|
+
| `csrf.allowedOrigins` | `string[]` | `[]` | Kendi host'umuzun yanında kabul edilen origin'ler. |
|
|
272
|
+
| `csrf.exclude` | `string[]` | `[]` | Kontrolden muaf yollar; `source` desen sözdizimi. |
|
|
273
|
+
| `csrf.cookieName` | `string` | `"csrf_token"` | Token cookie'sinin adı. |
|
|
274
|
+
| `csrf.fieldName` | `string` | `"_csrf"` | `csrfField()`in bastığı alan adı. |
|
|
275
|
+
| `csrf.headerName` | `string` | `"x-csrf-token"` | Token'ın kabul edildiği başlık. |
|
|
276
|
+
|
|
277
|
+
`trustProxy` doğrudan internete açık bir sunucuda **kapatılmalı**: açıkken
|
|
278
|
+
istemci kendi `X-Forwarded-For` başlığını uydurabilir ve rate limit ile audit
|
|
279
|
+
log yanlış adresi görür.
|
|
280
|
+
|
|
281
|
+
CSRF kontrolü yalnızca çapraz site olduğu **belli** olan istekleri reddeder —
|
|
282
|
+
`Origin` uyuşmuyorsa ya da `Sec-Fetch-Site: cross-site` geldiyse. İkisi de yoksa
|
|
283
|
+
istek geçer, çünkü tarayıcılar çapraz origin bir POST'ta `Origin`'i her zaman
|
|
284
|
+
gönderirken webhook'lar hiç göndermez. Yine de tarayıcıdan gelmeyen uçları
|
|
285
|
+
`csrf.exclude` listesine yazmak niyeti okunur kılıyor.
|
|
286
|
+
|
|
242
287
|
## `navigation`
|
|
243
288
|
|
|
244
289
|
**Tip:** `object` — **Varsayılan:**
|
|
@@ -521,6 +566,10 @@ html: {
|
|
|
521
566
|
}
|
|
522
567
|
```
|
|
523
568
|
|
|
569
|
+
Tek istisna `route(fn, { private: true })`: bu route'ta desen eşleşse bile yok
|
|
570
|
+
sayılır. Kilidin tek yönlü olması bilinçli — ters yönde bir hata, bir
|
|
571
|
+
kullanıcının HTML'inin bir başkasına servis edilmesi anlamına geliyor.
|
|
572
|
+
|
|
524
573
|
### `cache().prewarm`
|
|
525
574
|
|
|
526
575
|
| Alan | Tip | Varsayılan | Anlamı |
|
|
@@ -622,6 +671,7 @@ basılmaz.
|
|
|
622
671
|
| `NODE_ENV` | her yer | `production` (start/build), `development` (dev) | Dev overlay, EJS cache, manifest yeniden okuma, route hata davranışı ve prewarm varsayılanlarını belirler. `jskelet dev` bunu kendisi ayarlar — `cross-env` gerekmez. |
|
|
623
672
|
| `PORT` | `startServer` | `3000` | Dinlenecek port |
|
|
624
673
|
| `HOST` | `startServer` | `0.0.0.0` | Bağlanılacak arayüz |
|
|
674
|
+
| `JSKELET_SECRET` | `jskelet/cookies` | — | İmzalı cookie sırrı. `security.cookieSecret` verilmediğinde buradan okunur; ikisi de yoksa imzalı cookie API'si hata verir. [12](./12-panel-ve-oturum.md) |
|
|
625
675
|
| `DEV_TOKEN` | `devGate`, `prewarm` | — | Ayarlıysa token taşımayan her isteğe 404 döner. Isıtma token'ı çerez olarak taşır. [09](./09-dev-araclari.md) |
|
|
626
676
|
| `PREWARM` | `startPrewarm` | — | `0` ısıtmayı kapatır; `1` config'teki `enabled: false`'u ezip açar |
|
|
627
677
|
| `PREWARM_MAX` | `prewarm` | `400` | En fazla kaç yol ısıtılır |
|
package/docs/08-build.md
CHANGED
|
@@ -69,7 +69,7 @@ dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
|
|
|
69
69
|
Build çalışmadıysa uygulama yine ayağa kalkar: `hasAsset()` false olur, layout
|
|
70
70
|
stylesheet ve script etiketlerini hiç basmaz. `jskelet build` unutulduğunda hata
|
|
71
71
|
yerine stilsiz ama çalışan bir sayfa görürsünüz. Manifest hiç yoksa bir kez uyarı
|
|
72
|
-
basılır:
|
|
72
|
+
basılır: ``[assets] no manifest — run `jskelet build`.``
|
|
73
73
|
|
|
74
74
|
Manifest **dev'de her istekte** yeniden okunur (watch build hash'leri
|
|
75
75
|
değiştirir), prod'da bir kez.
|
|
@@ -244,8 +244,8 @@ Son satır için iki güvenlik ağı var: ad taşıyan yapılandırma alanları
|
|
|
244
244
|
sembolleri okuyup eksik olan için tek seferlik uyarı basar:
|
|
245
245
|
|
|
246
246
|
```
|
|
247
|
-
[icon] sprite
|
|
248
|
-
build/tasks/icons.mjs
|
|
247
|
+
[icon] missing from sprite: x-logo-regular — write the name as a literal or add
|
|
248
|
+
it to the build/tasks/icons.mjs scan.
|
|
249
249
|
```
|
|
250
250
|
|
|
251
251
|
Bu uyarıyı görürseniz ya adı sabit yazın, ya `icons.scan` listesine ilgili
|
|
@@ -352,7 +352,7 @@ karşılaşmaması.
|
|
|
352
352
|
kontrol edin.
|
|
353
353
|
- **Bazı Tailwind sınıfları çalışmıyor.** `@source` direktifi eksik olan bir
|
|
354
354
|
dizinde yazılmışlar.
|
|
355
|
-
- **İkon boş görünüyor.** Sprite'ta o sembol yok; dev'de `[icon] sprite
|
|
355
|
+
- **İkon boş görünüyor.** Sprite'ta o sembol yok; dev'de `[icon] missing from sprite`
|
|
356
356
|
uyarısına bakın.
|
|
357
357
|
- **Island'lar hiç açılmıyor.** `main.js` manifest'te yok (entry dizini boş ya
|
|
358
358
|
da build atlanmış) veya bir build hatası var.
|
package/docs/09-dev-araclari.md
CHANGED
|
@@ -161,6 +161,11 @@ Gösterdikleri:
|
|
|
161
161
|
- **Prewarm:** ısıtma turunun ilerlemesi; panelden elle tetiklenebilir, tek tek
|
|
162
162
|
yollar tekrar denenebilir.
|
|
163
163
|
- **Süreç:** pid, Node sürümü, uptime, RSS ve heap kullanımı.
|
|
164
|
+
- **Sürüm:** kurulu JSkelet sürümü ve npm'deki `latest` ile karşılaştırması.
|
|
165
|
+
Yeni bir sürüm varsa **Server** sekmesinde `update` rozeti ve yükseltme
|
|
166
|
+
komutunu kopyalayan bir satır çıkar. Yoklama açılıştan 1,5 saniye sonra bir
|
|
167
|
+
kez yapılır, sonucu 6 saat boyunca `os.tmpdir()` içinde saklanır ve ağ yoksa
|
|
168
|
+
sessizce atlanır. `JSKELET_VERSION_CHECK=0` ile tamamen kapatılır.
|
|
164
169
|
|
|
165
170
|
Isıtma istekleri (`user-agent: jskelet-prewarm`) hem terminalden hem istek
|
|
166
171
|
listesinden filtrelenir: yüzlerce istek görünümü doldurmasın. İlerleme baloncuğun
|