jskelet 0.5.5 → 0.6.1

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.
Files changed (156) hide show
  1. package/AGENTS.md +19 -15
  2. package/CHANGELOG.md +165 -15
  3. package/README.md +16 -21
  4. package/bin/jskelet.mjs +23 -9
  5. package/docs/01-baslangic.md +4 -3
  6. package/docs/02-mimari.md +10 -4
  7. package/docs/03-routing.md +14 -7
  8. package/docs/04-render-ve-sablonlar.md +60 -43
  9. package/docs/05-islands.md +12 -8
  10. package/docs/06-cache.md +18 -7
  11. package/docs/07-yapilandirma.md +69 -27
  12. package/docs/08-build.md +15 -9
  13. package/docs/09-dev-araclari.md +22 -8
  14. package/docs/10-dagitim.md +14 -13
  15. package/docs/11-tasima.md +51 -17
  16. package/docs/12-panel-ve-oturum.md +10 -4
  17. package/docs/README.md +10 -33
  18. package/docs/en/01-getting-started.md +4 -3
  19. package/docs/en/02-architecture.md +12 -6
  20. package/docs/en/03-routing.md +15 -8
  21. package/docs/en/04-rendering.md +71 -59
  22. package/docs/en/05-islands.md +13 -8
  23. package/docs/en/06-caching.md +21 -7
  24. package/docs/en/07-configuration.md +69 -29
  25. package/docs/en/08-build.md +16 -10
  26. package/docs/en/09-dev-tools.md +24 -8
  27. package/docs/en/10-deployment.md +14 -14
  28. package/docs/en/11-migration.md +51 -16
  29. package/docs/en/12-dashboards-and-sessions.md +9 -4
  30. package/docs/en/README.md +10 -35
  31. package/package.json +48 -13
  32. package/src/build/tasks/client.mjs +91 -10
  33. package/src/build/tasks/icons.mjs +11 -1
  34. package/src/client/index.js +2 -2
  35. package/src/compile/codegen.js +4 -0
  36. package/src/compile/compile-all.js +12 -21
  37. package/src/compile/expr.js +5 -0
  38. package/src/compile/parse.js +64 -8
  39. package/src/compile/resolve.js +3 -0
  40. package/src/config/defaults.js +48 -5
  41. package/src/config/index.js +138 -27
  42. package/src/dev-server.mjs +26 -3
  43. package/src/http/cookies-entry.js +1 -0
  44. package/src/http/cookies.js +18 -0
  45. package/src/logo.png +0 -0
  46. package/src/migrate/apply.mjs +262 -0
  47. package/src/migrate/babel.mjs +79 -0
  48. package/src/migrate/classify.mjs +155 -0
  49. package/src/migrate/config.mjs +126 -0
  50. package/src/migrate/fs-walk.mjs +191 -0
  51. package/src/migrate/parse.mjs +26 -0
  52. package/src/migrate/scan.mjs +177 -0
  53. package/src/migrate/transform/expr-source.mjs +168 -0
  54. package/src/migrate/transform/island.mjs +67 -0
  55. package/src/migrate/transform/jsx-to-component.mjs +302 -0
  56. package/src/migrate/transform/jsx-to-jsk.mjs +330 -0
  57. package/src/migrate/transform/page-split.mjs +435 -0
  58. package/src/migrate/write.mjs +81 -0
  59. package/src/migrate.mjs +171 -0
  60. package/src/server/auth/handoff.js +94 -11
  61. package/src/server/create-app.js +37 -10
  62. package/src/server/ejs-adapter.js +59 -0
  63. package/src/server/html-cache.js +178 -32
  64. package/src/server/image-optimizer.js +94 -26
  65. package/src/server/middleware/dev-gate.js +21 -8
  66. package/src/server/middleware/robots-txt.js +341 -0
  67. package/src/server/port-guard.js +255 -0
  68. package/src/server/prewarm.js +137 -51
  69. package/src/server/render.js +30 -10
  70. package/src/server/status-page.js +105 -4
  71. package/src/start.mjs +18 -3
  72. package/src/templates/layout.ejs +8 -28
  73. package/src/templates/layout.jsk +30 -0
  74. package/src/templates/layout.render.js +41 -0
  75. package/src/views/helpers/tags.js +86 -3
  76. package/types/build/resolve-peer.d.mts +13 -0
  77. package/types/client/dom.d.ts +55 -0
  78. package/types/client/form.d.ts +19 -0
  79. package/types/client/index.d.ts +20 -0
  80. package/types/client/registry.d.ts +53 -0
  81. package/types/client/safe-image.d.ts +19 -0
  82. package/types/client/shared-cookie.d.ts +82 -0
  83. package/types/client/store.d.ts +18 -0
  84. package/types/client/swap.d.ts +46 -0
  85. package/types/compile/codegen.d.ts +32 -0
  86. package/types/compile/compile-all.d.ts +42 -0
  87. package/types/compile/errors.d.ts +30 -0
  88. package/types/compile/expr.d.ts +67 -0
  89. package/types/compile/index.d.ts +10 -0
  90. package/types/compile/parse.d.ts +82 -0
  91. package/types/compile/resolve.d.ts +46 -0
  92. package/types/compile/scan-exports.d.ts +9 -0
  93. package/types/config/defaults.d.ts +477 -0
  94. package/types/config/index.d.ts +304 -0
  95. package/types/config/pattern.d.ts +38 -0
  96. package/types/http/control-flow.d.ts +45 -0
  97. package/types/http/cookies-entry.d.ts +5 -0
  98. package/types/http/cookies.d.ts +113 -0
  99. package/types/http/request-cache.d.ts +13 -0
  100. package/types/http/request-context.d.ts +67 -0
  101. package/types/http/shared-cookie.d.ts +73 -0
  102. package/types/index.d.ts +30 -0
  103. package/types/log.d.mts +153 -0
  104. package/types/server/admin/actions.d.ts +16 -0
  105. package/types/server/admin/auth.d.ts +52 -0
  106. package/types/server/admin/event-log.d.ts +38 -0
  107. package/types/server/admin/gate.d.ts +43 -0
  108. package/types/server/admin/inventory.d.ts +40 -0
  109. package/types/server/admin/mount.d.ts +6 -0
  110. package/types/server/admin/router.d.ts +6 -0
  111. package/types/server/admin/snapshot.d.ts +6 -0
  112. package/types/server/assets.d.ts +47 -0
  113. package/types/server/auth/handoff.d.ts +12 -0
  114. package/types/server/cache-deps.d.ts +16 -0
  115. package/types/server/cache-vary.d.ts +30 -0
  116. package/types/server/cloudflare.d.ts +163 -0
  117. package/types/server/create-app.d.ts +25 -0
  118. package/types/server/data-cache.d.ts +116 -0
  119. package/types/server/dev/devtools.d.ts +44 -0
  120. package/types/server/dev/report.d.ts +229 -0
  121. package/types/server/dev/socket.d.ts +17 -0
  122. package/types/server/dev/version-check.d.mts +15 -0
  123. package/types/server/ejs-adapter.d.ts +11 -0
  124. package/types/server/head-hints.d.ts +40 -0
  125. package/types/server/html-cache.d.ts +207 -0
  126. package/types/server/image-optimizer.d.ts +68 -0
  127. package/types/server/logs/access-middleware.d.ts +7 -0
  128. package/types/server/logs/file-sink.d.ts +17 -0
  129. package/types/server/logs/pipeline.d.ts +37 -0
  130. package/types/server/logs/s3-put.d.ts +85 -0
  131. package/types/server/logs/s3-sink.d.ts +26 -0
  132. package/types/server/metadata.d.ts +38 -0
  133. package/types/server/middleware/compression.d.ts +17 -0
  134. package/types/server/middleware/csrf.d.ts +4 -0
  135. package/types/server/middleware/dev-gate.d.ts +2 -0
  136. package/types/server/middleware/headers.d.ts +2 -0
  137. package/types/server/middleware/redirects.d.ts +2 -0
  138. package/types/server/middleware/robots-txt.d.ts +33 -0
  139. package/types/server/middleware/static-precompressed.d.ts +5 -0
  140. package/types/server/middleware/trailing-slash.d.ts +11 -0
  141. package/types/server/middleware/upstream-proxy.d.ts +21 -0
  142. package/types/server/og-image.d.ts +149 -0
  143. package/types/server/port-guard.d.ts +50 -0
  144. package/types/server/prewarm.d.ts +131 -0
  145. package/types/server/redis.d.ts +163 -0
  146. package/types/server/render.d.ts +101 -0
  147. package/types/server/router.d.ts +5 -0
  148. package/types/server/status-page.d.ts +24 -0
  149. package/types/server/upstream-limiter.d.ts +123 -0
  150. package/types/server/upstream-tracking.d.ts +42 -0
  151. package/types/shared/cookie-domain.d.ts +29 -0
  152. package/types/templates/layout.render.d.ts +7 -0
  153. package/types/version.d.mts +10 -0
  154. package/types/views/components/loader.d.ts +5 -0
  155. package/types/views/helpers/html.d.ts +39 -0
  156. package/types/views/helpers/tags.d.ts +127 -0
@@ -5,11 +5,12 @@
5
5
  * "Internal Server Error" çıktısını görmesi istenmiyor; bu yüzden framework
6
6
  * şablonsuz, tek dosyada duran minimal bir HTML üretir.
7
7
  *
8
- * Metin bilinçli olarak yalın: yalnızca bir şeyin ters gittiğini söyler.
9
- * Marka adı, ürün tanıtımı ya da hata ayrıntısı yok — hata sayfası
10
- * ziyaretçiye bir şey satmaz ve sunucunun içini dışa açmaz.
8
+ * Production metni bilinçli olarak yalın: yalnızca bir şeyin ters gittiğini
9
+ * söyler. Marka adı, ürün tanıtımı ya da hata ayrıntısı yok — hata sayfası
10
+ * ziyaretçiye bir şey satmaz ve sunucunun içini dışa açmaz. Development'ta
11
+ * 5xx için ayrıntılı teşhis sayfası üretilir (aşağıya bak).
11
12
  *
12
- * Ezme yolları (öncelik sırasıyla):
13
+ * Ezme yolları (öncelik sırasıyla; development 5xx hariç):
13
14
  * 1. `hooks.notFound()` — yalnızca 404 için, geriye dönük uyumluluk.
14
15
  * 2. `hooks.error({ status })` — tüm durumlar için; sayfa tanımı ya da
15
16
  * doğrudan HTML string döner.
@@ -23,11 +24,19 @@ import { esc } from "../views/helpers/html.js";
23
24
  * hata sayfasının kendisi patlarsa ziyaretçi boş yanıt görür, bu yüzden her
24
25
  * başarısızlık gömülü HTML'e düşer.
25
26
  *
27
+ * Development'ta 5xx yanıtları `hooks.error` ve gömülü 500 sayfasını atlar;
28
+ * yığın izini içeren bir teşhis sayfası döner. Production'da ayrıntı
29
+ * gösterilmez — sunucu içi ziyaretçiye açılmaz.
30
+ *
26
31
  * @param {number} status
27
32
  * @param {{ error?: unknown }} [options]
28
33
  * @returns {Promise<string>}
29
34
  */
30
35
  export async function renderStatusPage(status, options = {}) {
36
+ if (process.env.NODE_ENV === "development" && status >= 500) {
37
+ return developmentErrorPage(status, options.error);
38
+ }
39
+
31
40
  const page =
32
41
  (status === 404 ? await hook("notFound", null) : null) ??
33
42
  (await hook("error", null, { status, error: options.error }));
@@ -162,3 +171,95 @@ p { margin: 0.375rem 0 0; color: var(--muted); }
162
171
  </body>
163
172
  </html>`;
164
173
  }
174
+
175
+ /**
176
+ * Development teşhis sayfası: mesaj + yığın izi. Yalnızca
177
+ * `NODE_ENV=development` iken `renderStatusPage` üzerinden çağrılır;
178
+ * production yolu buraya hiç girmez.
179
+ *
180
+ * @param {number} status
181
+ * @param {unknown} [error]
182
+ * @returns {string}
183
+ */
184
+ function developmentErrorPage(status, error) {
185
+ const name =
186
+ error instanceof Error && error.name ? error.name : "Error";
187
+ const message =
188
+ error instanceof Error
189
+ ? error.message || "(no message)"
190
+ : error == null
191
+ ? "(no error object)"
192
+ : String(error);
193
+ const body = serializeError(error);
194
+
195
+ return `<!DOCTYPE html>
196
+ <html lang="en">
197
+ <head>
198
+ <meta charset="utf-8">
199
+ <meta name="viewport" content="width=device-width, initial-scale=1">
200
+ <meta name="robots" content="noindex, nofollow">
201
+ <title>${status} — ${esc(name)}: ${esc(message)}</title>
202
+ <style>
203
+ :root { color-scheme: light dark; --fg: #18181b; --muted: #71717a; --bg: #fafafa; --panel: #fff; --border: #e4e4e7; }
204
+ @media (prefers-color-scheme: dark) {
205
+ :root { --fg: #f4f4f5; --muted: #a1a1aa; --bg: #09090b; --panel: #18181b; --border: #27272a; }
206
+ }
207
+ html, body { margin: 0; background: var(--bg); color: var(--fg); }
208
+ body {
209
+ padding: 1.5rem; max-width: 56rem; margin: 0 auto;
210
+ font: 1rem/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
211
+ }
212
+ .banner {
213
+ display: inline-block; margin: 0 0 1rem; padding: 0.2rem 0.5rem;
214
+ font-size: 0.75rem; font-weight: 600; letter-spacing: 0.04em;
215
+ text-transform: uppercase; color: var(--muted); border: 1px solid var(--border);
216
+ }
217
+ .code { font-size: 2.5rem; font-weight: 600; letter-spacing: -0.02em; margin: 0; }
218
+ h1 { font-size: 1.25rem; font-weight: 600; margin: 0.5rem 0 0; word-break: break-word; }
219
+ pre {
220
+ margin: 1.25rem 0 0; padding: 1rem; overflow: auto;
221
+ background: var(--panel); border: 1px solid var(--border);
222
+ font: 0.8125rem/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
223
+ white-space: pre-wrap; word-break: break-word;
224
+ }
225
+ </style>
226
+ </head>
227
+ <body>
228
+ <p class="banner">Development only — not shown in production</p>
229
+ <p class="code">${status}</p>
230
+ <h1>${esc(name)}: ${esc(message)}</h1>
231
+ <pre>${esc(body)}</pre>
232
+ </body>
233
+ </html>`;
234
+ }
235
+
236
+ /**
237
+ * Hata nesnesini yığın izi + `cause` zinciriyle metne çevirir.
238
+ *
239
+ * @param {unknown} error
240
+ * @returns {string}
241
+ */
242
+ function serializeError(error) {
243
+ if (error == null) return "(no error object)";
244
+
245
+ /** @type {string[]} */
246
+ const parts = [];
247
+ /** @type {unknown} */
248
+ let current = error;
249
+ let depth = 0;
250
+
251
+ while (current != null && depth < 6) {
252
+ if (depth > 0) parts.push("\nCaused by:");
253
+
254
+ if (current instanceof Error) {
255
+ parts.push(current.stack || `${current.name}: ${current.message}`);
256
+ current = "cause" in current ? current.cause : undefined;
257
+ } else {
258
+ parts.push(String(current));
259
+ break;
260
+ }
261
+ depth += 1;
262
+ }
263
+
264
+ return parts.join("\n");
265
+ }
package/src/start.mjs CHANGED
@@ -1,7 +1,22 @@
1
1
  /**
2
2
  * Prod sunucu girişi. Build çıktısı yoksa önce üretir, sonra dinlemeye başlar.
3
+ *
4
+ * `jskelet start --murder` → dolu porttaki dinleyiciyi öldürüp başlar.
5
+ * Port kontrolü build'den önce: doluysa pahalı üretimi hiç başlatma.
3
6
  */
4
- import "./build/ensure-build.mjs";
5
- import { startServer } from "./server/create-app.js";
7
+ import process from "node:process";
8
+ import { ensurePortFree } from "./server/port-guard.js";
6
9
 
7
- await startServer();
10
+ const port = Number(process.env.PORT ?? 3000);
11
+ const murder = process.argv.includes("--murder");
12
+
13
+ try {
14
+ await ensurePortFree(port, { murder });
15
+ await import("./build/ensure-build.mjs");
16
+ const { startServer } = await import("./server/create-app.js");
17
+ await startServer({ murder, port });
18
+ } catch (error) {
19
+ const message = error instanceof Error ? error.message : String(error);
20
+ process.stderr.write(`${message}\n`);
21
+ process.exit(1);
22
+ }
@@ -1,9 +1,10 @@
1
1
  <%#
2
- JSkelet varsayılan layout'u.
2
+ JSkelet varsayılan layout'unun legacy EJS kopyası.
3
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.
4
+ Yeni uygulamalar `jskelet/layout` (.jsk) kullanır. Bu dosya yalnızca EJS
5
+ peer'i kurulu eski şablonlar için `jskelet/layout/ejs` üzerinden erişilir.
6
+ Uygulama `views/layout.jsk` veya `views/layout.ejs` oluşturduğu anda
7
+ framework layout'u devre dışı kalır.
7
8
 
8
9
  Kullanılabilir local'ler: metadata, headMeta, extraHead, structuredData,
9
10
  body, bodyClass, entries, styles, pathname, lang, devtools, devBasePath,
@@ -18,33 +19,12 @@
18
19
  <%# Kaynak ipuçları en başta: preconnect ve LCP preload'ını geciktirmek
19
20
  doğrudan LCP'ye yazılır. Sayfaya özel head buradan gelir. %>
20
21
  <%- extraHead %>
21
- <%# Global sheet + controller `styles: [...]` sayfa sheet'leri.
22
- data-jskelet-css: dev hot-swap doğru link'i bulsun diye. %>
23
- <% if (hasAsset('app.css')) { %>
24
- <link rel="stylesheet" href="<%= asset('app.css') %>" data-jskelet-css="app.css">
25
- <% } %>
26
- <% styles.forEach(function (sheet) { %>
27
- <% if (hasAsset(sheet)) { %>
28
- <link rel="stylesheet" href="<%= asset(sheet) %>" data-jskelet-css="<%= sheet %>">
29
- <% } %>
30
- <% }); %>
22
+ <%- stylesheets({ styles }) %>
31
23
  <%- headMeta %>
32
- <% structuredData.forEach(function (item) { %>
33
- <script type="application/ld+json"><%- jsonScript(item) %></script>
34
- <% }); %>
24
+ <%- jsonLd({ items: structuredData }) %>
35
25
  </head>
36
26
  <body class="<%= bodyClass %>">
37
27
  <%- body %>
38
- <%# Island runtime; entry yoksa sayfa yine tam çalışır (sadece JS'siz). %>
39
- <% if (hasAsset('main.js')) { %>
40
- <script type="module" src="<%= asset('main.js') %>"></script>
41
- <% } %>
42
- <% entries.forEach(function (entry) { %>
43
- <script type="module" src="<%= asset(entry) %>"></script>
44
- <% }); %>
45
- <%# Dev overlay: prod build'de yok, yalnızca `jskelet dev` sunucusundan. %>
46
- <% if (devtools) { %>
47
- <script type="module" src="<%= devBasePath %>/overlay.js"></script>
48
- <% } %>
28
+ <%- bodyScripts({ entries, devtools, devBasePath }) %>
49
29
  </body>
50
30
  </html>
@@ -0,0 +1,30 @@
1
+ {#
2
+ JSkelet varsayılan layout'u.
3
+
4
+ Uygulama `views/layout.jsk` (veya legacy `views/layout.ejs`) oluşturduğu
5
+ anda bu dosya devre dışı kalır; amacı yeni bir projenin tek route ile
6
+ çalışabilmesi. Kopyalayıp başlangıç noktası olarak kullanın.
7
+
8
+ Kullanılabilir local'ler: metadata, headMeta, extraHead, structuredData,
9
+ body, bodyClass, entries, styles, pathname, lang, devtools, devBasePath,
10
+ asset, 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
+ Stylesheets / JsonLd / BodyScripts: `.jsk` ifade dilinde fonksiyon çağrısı
14
+ olmadığı için asset döngüleri bu yerleşik etiketlere taşındı.
15
+ #}
16
+ <!DOCTYPE html>
17
+ <html :lang="lang">
18
+ <head>
19
+ <meta charset="utf-8">
20
+ <meta name="viewport" content="width=device-width, initial-scale=1">
21
+ {{{ extraHead }}}
22
+ <Stylesheets :styles="styles" />
23
+ {{{ headMeta }}}
24
+ <JsonLd :items="structuredData" />
25
+ </head>
26
+ <body :class="bodyClass">
27
+ {{{ body }}}
28
+ <BodyScripts :entries="entries" :devtools="devtools" :devBasePath="devBasePath" />
29
+ </body>
30
+ </html>
@@ -0,0 +1,41 @@
1
+ /** @generated by scripts/compile-framework-layout.mjs — do not edit */
2
+ /**
3
+ * @param {Record<string, unknown>} data
4
+ * @param {Record<string, any>} helpers
5
+ * @returns {string}
6
+ */
7
+ export function render(data, helpers) {
8
+ let s = "";
9
+ s += "\r\n";
10
+ s += "<!DOCTYPE html>";
11
+ s += "\r\n";
12
+ s += "<html" + helpers.attrs({"lang": data.lang}) + ">";
13
+ s += "\r\n ";
14
+ s += "<head>";
15
+ s += "\r\n ";
16
+ s += "<meta charset=\"utf-8\">";
17
+ s += "\r\n ";
18
+ s += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
19
+ s += "\r\n ";
20
+ s += (data.extraHead ?? "");
21
+ s += "\r\n ";
22
+ s += (helpers.Stylesheets({ "styles": data.styles }) ?? "");
23
+ s += "\r\n ";
24
+ s += (data.headMeta ?? "");
25
+ s += "\r\n ";
26
+ s += (helpers.JsonLd({ "items": data.structuredData }) ?? "");
27
+ s += "\r\n ";
28
+ s += "</head>";
29
+ s += "\r\n ";
30
+ s += "<body" + helpers.attrs({"class": data.bodyClass}) + ">";
31
+ s += "\r\n ";
32
+ s += (data.body ?? "");
33
+ s += "\r\n ";
34
+ s += (helpers.BodyScripts({ "entries": data.entries, "devtools": data.devtools, "devBasePath": data.devBasePath }) ?? "");
35
+ s += "\r\n ";
36
+ s += "</body>";
37
+ s += "\r\n";
38
+ s += "</html>";
39
+ s += "\r\n";
40
+ return s;
41
+ }
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * `next/link`, `next/image` ve `@phosphor-icons/react` karşılıkları.
3
- * Hepsi HTML string döndürür; EJS içinden `<%- %>` ile basılır.
3
+ * Hepsi HTML string döndürür; şablonlarda PascalCase etiket veya
4
+ * (legacy EJS) `<%- %>` ile basılır.
4
5
  */
5
- import { attrs, esc, cn } from "./html.js";
6
- import { asset, getSpriteIds, optimizedImage } from "../../server/assets.js";
6
+ import { attrs, esc, cn, jsonScript } from "./html.js";
7
+ import {
8
+ asset,
9
+ hasAsset,
10
+ getSpriteIds,
11
+ optimizedImage,
12
+ } from "../../server/assets.js";
7
13
  import {
8
14
  parseAllowedRemoteUrl,
9
15
  remoteImageUrl,
@@ -290,3 +296,80 @@ export function toKebab(name) {
290
296
  .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
291
297
  .toLowerCase();
292
298
  }
299
+
300
+ /**
301
+ * Layout `<head>` stylesheet'leri. `hasAsset` false olanları basmaz —
302
+ * build yokken 404 link'i istememek için. `.jsk` ifade dilinde `asset()` /
303
+ * `hasAsset()` çağrılamadığı için layout bu etiketi kullanır.
304
+ *
305
+ * @param {{ styles?: string[] }} [props]
306
+ * @returns {string}
307
+ */
308
+ export function stylesheets(props = {}) {
309
+ const styles = Array.isArray(props.styles) ? props.styles : [];
310
+ /** @type {string[]} */
311
+ const parts = [];
312
+
313
+ if (hasAsset("app.css")) {
314
+ parts.push(
315
+ `<link rel="stylesheet" href="${esc(asset("app.css"))}" data-jskelet-css="app.css">`,
316
+ );
317
+ }
318
+
319
+ for (const sheet of styles) {
320
+ if (!sheet || !hasAsset(sheet)) continue;
321
+ parts.push(
322
+ `<link rel="stylesheet" href="${esc(asset(sheet))}" data-jskelet-css="${esc(sheet)}">`,
323
+ );
324
+ }
325
+
326
+ return parts.join("\n ");
327
+ }
328
+
329
+ /**
330
+ * Layout gövde sonu script'leri: `main.js`, controller `entries`, isteğe
331
+ * bağlı dev overlay.
332
+ *
333
+ * @param {{ entries?: string[], devtools?: boolean, devBasePath?: string }} [props]
334
+ * @returns {string}
335
+ */
336
+ export function bodyScripts(props = {}) {
337
+ const entries = Array.isArray(props.entries) ? props.entries : [];
338
+ const devBasePath = props.devBasePath ?? "/__jskelet/dev";
339
+ /** @type {string[]} */
340
+ const parts = [];
341
+
342
+ if (hasAsset("main.js")) {
343
+ parts.push(`<script type="module" src="${esc(asset("main.js"))}"></script>`);
344
+ }
345
+
346
+ for (const entry of entries) {
347
+ if (!entry) continue;
348
+ parts.push(`<script type="module" src="${esc(asset(entry))}"></script>`);
349
+ }
350
+
351
+ if (props.devtools) {
352
+ parts.push(
353
+ `<script type="module" src="${esc(devBasePath)}/overlay.js"></script>`,
354
+ );
355
+ }
356
+
357
+ return parts.join("\n ");
358
+ }
359
+
360
+ /**
361
+ * JSON-LD script etiketleri. `structuredData` dizisindeki her öğe için bir
362
+ * `<script type="application/ld+json">`.
363
+ *
364
+ * @param {{ items?: unknown[] }} [props]
365
+ * @returns {string}
366
+ */
367
+ export function jsonLd(props = {}) {
368
+ const items = Array.isArray(props.items) ? props.items : [];
369
+ return items
370
+ .map(
371
+ (item) =>
372
+ `<script type="application/ld+json">${jsonScript(item)}</script>`,
373
+ )
374
+ .join("\n ");
375
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @param {string} root Uygulama kökü.
3
+ * @param {string} specifier
4
+ * @returns {Promise<any>}
5
+ * @throws Paket bulunamazsa; zorunlu bağımlılıklar için.
6
+ */
7
+ export declare function importFromApp(root: string, specifier: string): Promise<any>;
8
+ /**
9
+ * @param {string} root
10
+ * @param {string} specifier
11
+ * @returns {Promise<any | null>} Paket yoksa `null`; adım atlanabilsin diye.
12
+ */
13
+ export declare function tryImportFromApp(root: string, specifier: string): Promise<any | null>;
@@ -0,0 +1,55 @@
1
+ /** Island'ların paylaştığı küçük DOM yardımcıları. */
2
+ /**
3
+ * @param {ParentNode} root
4
+ * @param {string} selector
5
+ * @returns {HTMLElement | null}
6
+ */
7
+ export declare function qs(root: ParentNode, selector: string): HTMLElement | null;
8
+ /**
9
+ * @param {ParentNode} root
10
+ * @param {string} selector
11
+ * @returns {HTMLElement[]}
12
+ */
13
+ export declare function qsa(root: ParentNode, selector: string): HTMLElement[];
14
+ /**
15
+ * Otomatik temizlenebilir listener.
16
+ * @param {EventTarget} target
17
+ * @param {string} type
18
+ * @param {EventListenerOrEventListenerObject} handler
19
+ * @param {AddEventListenerOptions | boolean} [options]
20
+ * @returns {() => void}
21
+ */
22
+ export declare function on(target: EventTarget, type: string, handler: EventListenerOrEventListenerObject, options?: AddEventListenerOptions | boolean): () => void;
23
+ /**
24
+ * `data-*` üzerinden delege edilmiş click.
25
+ * @param {HTMLElement} root
26
+ * @param {string} selector
27
+ * @param {(event: MouseEvent, target: HTMLElement) => void} handler
28
+ * @returns {() => void}
29
+ */
30
+ export declare function onClick(root: HTMLElement, selector: string, handler: (event: MouseEvent, target: HTMLElement) => void): () => void;
31
+ /**
32
+ * @param {number} ms
33
+ * @param {(...args: any[]) => void} fn
34
+ * @returns {(...args: any[]) => void}
35
+ */
36
+ export declare function debounce(ms: number, fn: (...args: any[]) => void): (...args: any[]) => void;
37
+ /**
38
+ * requestAnimationFrame ile birleştirilmiş çağrı.
39
+ * @param {() => void} fn
40
+ * @returns {() => void}
41
+ */
42
+ export declare function raf(fn: () => void): () => void;
43
+ /**
44
+ * @param {HTMLElement} element
45
+ * @param {string} name
46
+ * @param {boolean} active
47
+ */
48
+ export declare function toggleClass(element: HTMLElement, name: string, active: boolean): void;
49
+ /**
50
+ * Overlay portal hedefi: layout'ta `<div id="jskelet-overlays">` varsa
51
+ * modal/drawer içeriği oraya taşınır, yoksa `body`ye. Portal, `overflow`
52
+ * ya da `transform` taşıyan bir ata elementin `position: fixed` overlay'i
53
+ * kırpmasını engeller.
54
+ */
55
+ export declare function getOverlayRoot(): HTMLElement;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Tek bir formu bağlar.
3
+ *
4
+ * `data-target` verilirse başarılı yanıtın gövdesi o bölgeye takılır; yoksa
5
+ * yanıt formun kendisini değiştirir (doğrulama hatalarının geldiği yol).
6
+ *
7
+ * @param {HTMLFormElement} form
8
+ * @returns {() => void} Dinleyiciyi söken fonksiyon.
9
+ */
10
+ export declare function enhanceForm(form: HTMLFormElement): () => void;
11
+ /**
12
+ * `data-enhance` taşıyan formları tek bir delege dinleyiciyle bağlar.
13
+ * `submit` olayı köpürdüğü için sonradan DOM'a giren (fragment ile gelen)
14
+ * formlar da kapsanır ve her takastan sonra yeniden bağlama gerekmez.
15
+ *
16
+ * @param {ParentNode} [root]
17
+ * @returns {() => void}
18
+ */
19
+ export declare function startForms(root?: ParentNode): () => void;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * JSkelet island runtime'ının tarayıcı tarafı API'si.
3
+ *
4
+ * `client/entries/main.js` (veya `main.ts`) içinde:
5
+ *
6
+ * import { registerAll, start } from "jskelet/client";
7
+ *
8
+ * registerAll({
9
+ * counter: () => import("../islands/counter.ts"),
10
+ * });
11
+ *
12
+ * start();
13
+ */
14
+ export { register, registerAll, hydrate, unmount, observeDocument, start, } from "./registry.js";
15
+ export { swap, startSwapLinks } from "./swap.js";
16
+ export { enhanceForm, startForms } from "./form.js";
17
+ export { createStore } from "./store.js";
18
+ export { debounce, on, onClick, qs, qsa, raf, toggleClass, getOverlayRoot, } from "./dom.js";
19
+ export { startSafeImages } from "./safe-image.js";
20
+ export { clearSharedCookie, consumeWindowNameHandoff, createHandoffUrl, handoffViaWindowName, readCookie, resolveSharedCookieDomain, SHARED_COOKIE_WARN_BYTES, writeSharedCookie, } from "./shared-cookie.js";
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Island registry.
3
+ *
4
+ * Sunucu HTML'i tamdır; JS yalnızca davranış ekler. Bir element
5
+ * `data-island="ad"` taşıdığında ilgili modül dinamik import edilir ve
6
+ * `mount(element, props)` çağrılır.
7
+ *
8
+ * Hidrasyon **varsayılan olarak görünürlüğe bağlıdır**: her island bir
9
+ * IntersectionObserver'a verilir, ekranda olanlar zaten ilk gözlemde tetiklenir,
10
+ * ekran dışındakiler kaydırılana kadar hiç indirilmez. Ana sayfadaki grafik
11
+ * kütüphanesi gibi ağır modüller böylece ilk yükten tamamen çıkar.
12
+ *
13
+ * `data-island-eager` görünürlükten bağımsız, hemen bağlanmayı zorlar
14
+ * (header, çerez bandı gibi global davranışlar için).
15
+ *
16
+ * `data-island-idle` görünür olsa bile sayfa sakinleşene (load + boş zaman)
17
+ * kadar bekletir. İlk ekranda görünen ama kritik olmayan ağır modüller
18
+ * (ör. grafik kütüphanesi çeken mini grafik) LCP ile yarışmasın diye.
19
+ *
20
+ * `data-island-props` JSON ise parse edilip props olarak geçer.
21
+ */
22
+ export type MountFn = (element: HTMLElement, props: object) => void | (() => void);
23
+ /**
24
+ * @param {string} name
25
+ * @param {() => Promise<{ mount: MountFn }>} loader
26
+ */
27
+ export declare function register(name: string, loader: () => Promise<{
28
+ mount: MountFn;
29
+ }>): void;
30
+ /** @param {Record<string, () => Promise<{ mount: MountFn }>>} entries */
31
+ export declare function registerAll(entries: Record<string, () => Promise<{
32
+ mount: MountFn;
33
+ }>>): void;
34
+ /**
35
+ * Bir alt ağaçtaki island'ları söker.
36
+ *
37
+ * Fragment takasında çağrılması zorunlu: `innerHTML` ile değiştirilen bir
38
+ * bölgenin island'ları DOM'dan çıkar ama `document`/`window` üzerine
39
+ * kurdukları dinleyiciler ve `setInterval`'ları yaşamaya devam eder. Birkaç
40
+ * takastan sonra aynı olay birden fazla kez işlenmeye başlar.
41
+ *
42
+ * Sökülen element yeniden bağlanabilir hâle gelir: `mounted` kaydı da
43
+ * temizlenir, böylece aynı düğüm tekrar DOM'a girerse `hydrate()` onu
44
+ * yeniden görür.
45
+ *
46
+ * @param {ParentNode} [root] Kökün kendisi de island olabilir.
47
+ */
48
+ export declare function unmount(root?: ParentNode): void;
49
+ /** @param {ParentNode} [root] */
50
+ export declare function hydrate(root?: ParentNode): void;
51
+ /** Sonradan DOM'a eklenen island'ları da yakalar (infinite scroll, portal). */
52
+ export declare function observeDocument(): MutationObserver;
53
+ export declare function start(): void;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Yüklenemeyen görseller için tek bir belge dinleyicisi.
3
+ *
4
+ * Bu iş bilinçli olarak **island değil**. Görsel ağırlıklı bir sayfada 80+
5
+ * `<img>` olabiliyor ve her birine ayrı island bağlamak (gözlemci + dinamik
6
+ * import + mount) sırf hata ihtimali için ciddi bir hidrasyon yükü. `error`
7
+ * olayı kabarmaz ama yakalama fazında görülebilir, bu yüzden tek dinleyici
8
+ * hepsini karşılar ve sonradan DOM'a eklenen görseller de kendiliğinden
9
+ * kapsanır.
10
+ *
11
+ * Kullanım: görsele `data-safe-image` ekleyin. Kendi hata görünümünüzü
12
+ * vermek için görseli `data-safe-image-host` taşıyan bir sarmalayıcıya alın
13
+ * ve içine `<template data-safe-image-fallback>` koyun — framework hiçbir
14
+ * stil dayatmaz, yalnızca değiştirme işini yapar.
15
+ */
16
+ /**
17
+ * @returns {void}
18
+ */
19
+ export declare function startSafeImages(): void;
@@ -0,0 +1,82 @@
1
+ export { SHARED_COOKIE_WARN_BYTES, resolveSharedCookieDomain, stripHostPort, } from "../shared/cookie-domain.js";
2
+ /**
3
+ * @param {string} name
4
+ * @returns {string | null}
5
+ */
6
+ export declare function readCookie(name: string): string | null;
7
+ export type ClientSharedCookieResult = {
8
+ ok: boolean;
9
+ domain: string | null;
10
+ handoff: boolean;
11
+ reason?: string;
12
+ };
13
+ /**
14
+ * @typedef {object} ClientSharedCookieResult
15
+ * @property {boolean} ok
16
+ * @property {string | null} domain
17
+ * @property {boolean} handoff
18
+ * @property {string} [reason]
19
+ */
20
+ /**
21
+ * @param {string} name
22
+ * @param {string} value
23
+ * @param {{ roots?: string[], maxAge?: number, path?: string, sameSite?: string,
24
+ * secure?: boolean }} [options]
25
+ * @returns {ClientSharedCookieResult}
26
+ */
27
+ export declare function writeSharedCookie(name: string, value: string, options?: {
28
+ roots?: string[];
29
+ maxAge?: number;
30
+ path?: string;
31
+ sameSite?: string;
32
+ secure?: boolean;
33
+ }): ClientSharedCookieResult;
34
+ /**
35
+ * @param {string} name
36
+ * @param {{ roots?: string[], path?: string, secure?: boolean, sameSite?: string }} [options]
37
+ * @returns {ClientSharedCookieResult}
38
+ */
39
+ export declare function clearSharedCookie(name: string, options?: {
40
+ roots?: string[];
41
+ path?: string;
42
+ secure?: boolean;
43
+ sameSite?: string;
44
+ }): ClientSharedCookieResult;
45
+ /**
46
+ * Handoff bileti ister; dönen URL'ye gidilir (`?handoff=` taşır).
47
+ *
48
+ * @param {{ name: string, value: string, next: string }} body
49
+ * @param {{ path?: string }} [options]
50
+ * @returns {Promise<string | null>}
51
+ */
52
+ export declare function createHandoffUrl(body: {
53
+ name: string;
54
+ value: string;
55
+ next: string;
56
+ }, options?: {
57
+ path?: string;
58
+ }): Promise<string | null>;
59
+ /**
60
+ * first-party `window.name` köprüsü — cookie Domain reddedilince yedek.
61
+ * Kaynak sayfada hedefe gitmeden önce çağırın; hedefte
62
+ * `consumeWindowNameHandoff` okur.
63
+ *
64
+ * @param {string} targetUrl
65
+ * @param {{ name: string, value: string, maxAge?: number }} cookie
66
+ * @returns {void}
67
+ */
68
+ export declare function handoffViaWindowName(targetUrl: string, cookie: {
69
+ name: string;
70
+ value: string;
71
+ maxAge?: number;
72
+ }): void;
73
+ /**
74
+ * Hedef host'ta `window.name` içindeki handoff'u host-only cookie olarak yazar.
75
+ *
76
+ * @param {{ write?: typeof writeSharedCookie }} [options]
77
+ * Varsayılan: Domain denemeden host-only `document.cookie`.
78
+ * @returns {boolean} Bir şey yazıldı mı.
79
+ */
80
+ export declare function consumeWindowNameHandoff(options?: {
81
+ write?: typeof writeSharedCookie;
82
+ }): boolean;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * React Context yerine kullanılan minimal pub/sub store.
3
+ * `useSyncExternalStore` köprüsünün yerini alır: doğrudan `subscribe`.
4
+ */
5
+ /**
6
+ * @param {T} initial
7
+ * @returns {{
8
+ * get: () => T,
9
+ * set: (next: T | ((prev: T) => T)) => void,
10
+ * subscribe: (listener: (value: T) => void) => () => void,
11
+ * }}
12
+ * @template T
13
+ */
14
+ export declare function createStore<T>(initial: T): {
15
+ get: () => T;
16
+ set: (next: T | ((prev: T) => T)) => void;
17
+ subscribe: (listener: (value: T) => void) => () => void;
18
+ };