jskelet 0.1.1 → 0.1.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 +5 -0
- package/CHANGELOG.md +129 -2
- 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 +240 -26
- package/docs/07-yapilandirma.md +108 -7
- 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 +640 -0
- package/docs/en/07-configuration.md +789 -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 +83 -0
- package/src/config/index.js +129 -18
- 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 +26 -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/data-cache.js +244 -0
- package/src/server/dev/devtools.js +6 -2
- package/src/server/dev/report.js +8 -1
- package/src/server/dev/version-check.mjs +139 -0
- package/src/server/head-hints.js +1 -1
- package/src/server/html-cache.js +32 -6
- package/src/server/middleware/csrf.js +134 -0
- package/src/server/prewarm.js +164 -19
- package/src/server/render.js +256 -20
- 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,10 +10,135 @@ one is listed under a **Breaking** heading.
|
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
|
+
- An upstream data cache: `withDataCache(key, ttlSeconds, producer)` and the
|
|
14
|
+
`dataCache(fn, { key, revalidate })` wrapper, with `clearDataCache(prefix?)`,
|
|
15
|
+
`getDataCacheSize()` and `getDataCacheEntries()` alongside them. It keeps JSON
|
|
16
|
+
rather than HTML, so its default limit is 10,000 entries: a long-tail page that
|
|
17
|
+
was never prewarmed still renders without touching the API. Concurrent reads of
|
|
18
|
+
the same key collapse into one upstream request, an expired entry is served
|
|
19
|
+
immediately while it refreshes in the background, a failing producer falls back
|
|
20
|
+
to the stale value, and empty answers (`null`/`undefined`) are not stored
|
|
21
|
+
unless `storeEmpty: true` is passed.
|
|
22
|
+
- `cache().prewarm.priority` decides the warm-up order and accepts both the
|
|
23
|
+
config pattern syntax (`/news/:slug`) and plain regular expressions. Matching
|
|
24
|
+
paths are warmed on every pass.
|
|
25
|
+
- Drip prewarming for large sites: `cache().prewarm.rps` (also `PREWARM_RPS`)
|
|
26
|
+
caps requests per second regardless of parallelism, and `rotate` (on by
|
|
27
|
+
default) makes periodic passes continue through the queue where the previous
|
|
28
|
+
one stopped instead of re-warming the same first slice. A pass is skipped while
|
|
29
|
+
the previous one is still running.
|
|
30
|
+
- `cache().prewarm.retryDelayMs` (also `PREWARM_RETRY_DELAY_MS`) waits before the
|
|
31
|
+
retry pass, since rate limit windows are measured in seconds.
|
|
32
|
+
- `cache().maxEntries` configures the HTML cache limit, which used to be a fixed
|
|
33
|
+
500.
|
|
34
|
+
- The dev report now includes the data cache entry count under `cache.data`.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- `notFound()` is no longer served as a 404 when a transient upstream failure
|
|
39
|
+
(`429`, `5xx`, network error) was reported during the same render. Those pages
|
|
40
|
+
now respond with an uncached `503` and `Retry-After`, so a temporary rate limit
|
|
41
|
+
is not frozen into "this page does not exist" for the whole TTL.
|
|
42
|
+
- Responses produced with missing data are no longer offered to shared caches:
|
|
43
|
+
a `degraded` render is sent with `private, no-store` instead of
|
|
44
|
+
`public, s-maxage=…`. The `X-JSkelet-Cache` diagnostic header is still written.
|
|
45
|
+
- The prewarm summary distinguishes paths left for the next pass
|
|
46
|
+
(`700 deferred to the next pass`) from paths dropped entirely
|
|
47
|
+
(`700 over the limit`).
|
|
48
|
+
- The changelog page of the marketing example is generated from the installed
|
|
49
|
+
package's `CHANGELOG.md` instead of a hand-written list, and shows the version
|
|
50
|
+
published on npm next to the installed one.
|
|
51
|
+
|
|
52
|
+
## [0.1.2] - 2026-08-30
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- `route(fn, { private: true })` for pages that depend on the visitor. The HTML
|
|
57
|
+
cache is bypassed, `cache.html` patterns can no longer turn caching on for
|
|
58
|
+
that route, and the response is sent with `private, no-store`, `Vary: Cookie`
|
|
59
|
+
and no ETag.
|
|
60
|
+
- A runtime guard against identity leaks: when a cacheable route reads
|
|
61
|
+
`Cookie`, `Authorization` or a session field, the rendered HTML is never
|
|
62
|
+
stored. In development the request fails with an explanation, in production it
|
|
63
|
+
is served with `no-store` and logged.
|
|
64
|
+
- `fragment()` for layout-less partial responses, with `no-store` and cache
|
|
65
|
+
bypass built in.
|
|
66
|
+
- CSRF protection. Cross-site state-changing requests are rejected based on
|
|
67
|
+
`Origin` and `Sec-Fetch-Site`; requests carrying neither header still pass, so
|
|
68
|
+
webhooks keep working. An optional double-submit token layer is enabled with
|
|
69
|
+
`security.csrf.token` and rendered into forms by the new `csrfField()` helper.
|
|
70
|
+
- Signed cookie helpers under `jskelet/cookies`: `parseCookies()`,
|
|
71
|
+
`setCookie()`, `clearCookie()`, `setSignedCookie()`, `getSignedCookie()`,
|
|
72
|
+
`randomToken()` and `safeEqual()`. Defaults are `HttpOnly`, `SameSite=Lax` and
|
|
73
|
+
`Secure` outside development.
|
|
74
|
+
- A `security` configuration section: `trustProxy`, `cookieSecret` and `csrf`.
|
|
75
|
+
- `seeOther()` for the post/redirect/get flow, which needs 303 rather than the
|
|
76
|
+
method-preserving 307 that `redirect()` sends.
|
|
77
|
+
- Island cleanup. A `mount()` function may return a teardown callback; it is now
|
|
78
|
+
stored and called by the new `unmount(root)` export when the subtree leaves
|
|
79
|
+
the DOM.
|
|
80
|
+
- Client helpers for partial updates: `swap()` and `startSwapLinks()` for
|
|
81
|
+
fetching and replacing a region, `enhanceForm()` and `startForms()` for
|
|
82
|
+
submitting forms without a full page load while keeping the no-JavaScript
|
|
83
|
+
path working.
|
|
84
|
+
- A fourth example, `examples/dashboard`: sign-in with a signed cookie session,
|
|
85
|
+
a private page, a paginated table fragment, a CSRF-protected mutation and an
|
|
86
|
+
island with cleanup, covered by its own `smoke.mjs`.
|
|
87
|
+
- An npm version badge in the `README`, linking to the package page.
|
|
88
|
+
- An English edition of the documentation under `docs/en/`, mirroring every
|
|
89
|
+
chapter of the Turkish `docs/`.
|
|
90
|
+
- The dev overlay now compares the installed version against the `latest` tag on
|
|
91
|
+
npm: the Server tab shows the version, marks an `update` chip when a newer
|
|
92
|
+
release exists and offers the upgrade command. The lookup is cached for six
|
|
93
|
+
hours, never blocks the server and can be turned off with
|
|
94
|
+
`JSKELET_VERSION_CHECK=0`.
|
|
95
|
+
|
|
96
|
+
### Changed
|
|
97
|
+
|
|
98
|
+
- `trust proxy` is now configurable through `security.trustProxy` instead of
|
|
99
|
+
being always on. The default is unchanged, but a server exposed directly to
|
|
100
|
+
the internet should turn it off: while it is on, a client can forge its own
|
|
101
|
+
`X-Forwarded-For` and rate limiting or audit logs see the wrong address.
|
|
102
|
+
- Every message the framework prints is now English: config, router, render,
|
|
103
|
+
cache, prewarm, asset and build warnings, CLI output, the project `jskelet
|
|
104
|
+
init` scaffolds, and the devtools overlay and report interfaces. Visitor-facing
|
|
105
|
+
status pages still follow `brand.lang` and keep their Turkish translations.
|
|
106
|
+
- The dev overlay and report now show the current JSkelet logo, served with a
|
|
107
|
+
cacheable response instead of being re-fetched on every navigation.
|
|
108
|
+
|
|
109
|
+
### Fixed
|
|
110
|
+
|
|
111
|
+
- A page rendered without `revalidate` used to be sent with no `Cache-Control`
|
|
112
|
+
at all, while still carrying a strong ETag. HTTP treats such a response as
|
|
113
|
+
heuristically cacheable, so an intermediate proxy or the browser's back button
|
|
114
|
+
could store a response meant for a single visitor. Dynamic pages now send
|
|
115
|
+
`private, no-store` and no ETag.
|
|
116
|
+
- A redirect thrown from a route that reads the session is no longer cacheable
|
|
117
|
+
either; a stored "you need to sign in" redirect used to follow the visitor
|
|
118
|
+
even after signing in.
|
|
119
|
+
|
|
120
|
+
## [0.1.1] - 2026-08-30
|
|
121
|
+
|
|
122
|
+
### Added
|
|
123
|
+
|
|
13
124
|
- English `README`, plus `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`,
|
|
14
125
|
a `LICENSE` file, issue and pull request templates, and a CI workflow.
|
|
126
|
+
- An English-first `examples/marketing` with a Turkish translation, serving the
|
|
127
|
+
package documentation under `/docs` and reading its version, dependencies and
|
|
128
|
+
bundle sizes from the installed package.
|
|
129
|
+
|
|
130
|
+
### Changed
|
|
131
|
+
|
|
132
|
+
- The install instructions point at the npm package instead of the git
|
|
133
|
+
repository.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
|
|
137
|
+
- No more white flash between pages: the page background moved onto the root
|
|
138
|
+
element, so it applies before the body paints. Reduced-motion preferences now
|
|
139
|
+
switch off the decorative animations as well, not just page transitions.
|
|
15
140
|
|
|
16
|
-
## [0.1.0]
|
|
141
|
+
## [0.1.0] - 2026-08-30
|
|
17
142
|
|
|
18
143
|
Initial release.
|
|
19
144
|
|
|
@@ -36,5 +161,7 @@ Initial release.
|
|
|
36
161
|
- Documentation under `docs/` and three examples: `minimal`, `blog`,
|
|
37
162
|
`marketing`.
|
|
38
163
|
|
|
39
|
-
[Unreleased]: https://github.com/ayberkenis/jskelet/compare/v0.1.
|
|
164
|
+
[Unreleased]: https://github.com/ayberkenis/jskelet/compare/v0.1.2...HEAD
|
|
165
|
+
[0.1.2]: https://github.com/ayberkenis/jskelet/compare/v0.1.1...v0.1.2
|
|
166
|
+
[0.1.1]: https://github.com/ayberkenis/jskelet/compare/v0.1.0...v0.1.1
|
|
40
167
|
[0.1.0]: https://github.com/ayberkenis/jskelet/releases/tag/v0.1.0
|
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
|
|