jskelet 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/AGENTS.md +136 -136
  2. package/CHANGELOG.md +620 -596
  3. package/LICENSE +21 -21
  4. package/bin/jskelet.mjs +130 -130
  5. package/docs/01-baslangic.md +291 -291
  6. package/docs/02-mimari.md +310 -309
  7. package/docs/03-routing.md +515 -515
  8. package/docs/04-render-ve-sablonlar.md +661 -661
  9. package/docs/05-islands.md +486 -486
  10. package/docs/06-cache.md +1443 -1423
  11. package/docs/07-yapilandirma.md +12 -6
  12. package/docs/08-build.md +429 -428
  13. package/docs/09-dev-araclari.md +364 -364
  14. package/docs/10-dagitim.md +338 -338
  15. package/docs/12-panel-ve-oturum.md +478 -478
  16. package/docs/README.md +83 -83
  17. package/docs/en/01-getting-started.md +298 -298
  18. package/docs/en/02-architecture.md +329 -328
  19. package/docs/en/03-routing.md +531 -531
  20. package/docs/en/04-rendering.md +669 -669
  21. package/docs/en/05-islands.md +497 -497
  22. package/docs/en/06-caching.md +1453 -1431
  23. package/docs/en/07-configuration.md +1219 -1214
  24. package/docs/en/08-build.md +447 -446
  25. package/docs/en/09-dev-tools.md +373 -373
  26. package/docs/en/10-deployment.md +340 -340
  27. package/docs/en/11-migration.md +398 -398
  28. package/docs/en/12-dashboards-and-sessions.md +488 -488
  29. package/docs/en/README.md +87 -87
  30. package/package.json +137 -137
  31. package/src/build/ensure-build.mjs +19 -19
  32. package/src/build/paths.mjs +153 -153
  33. package/src/build/resolve-peer.mjs +36 -36
  34. package/src/build/tasks/client.mjs +349 -349
  35. package/src/build/tasks/css.mjs +235 -235
  36. package/src/build/tasks/fonts.mjs +146 -146
  37. package/src/build/tasks/icons.mjs +357 -357
  38. package/src/build/tasks/images.mjs +244 -244
  39. package/src/build/tasks/precompress.mjs +78 -78
  40. package/src/build/tasks/templates.mjs +20 -20
  41. package/src/client/admin/i18n.js +764 -764
  42. package/src/client/admin/login.html +74 -74
  43. package/src/client/admin/panel.css +809 -809
  44. package/src/client/admin/panel.html +495 -495
  45. package/src/client/admin/panel.js +1251 -1251
  46. package/src/client/devtools/report.html +185 -185
  47. package/src/client/devtools/report.js +745 -745
  48. package/src/client/devtools/seo.js +628 -628
  49. package/src/client/dom.js +95 -95
  50. package/src/client/form.js +192 -192
  51. package/src/client/index.js +45 -45
  52. package/src/client/registry.js +305 -305
  53. package/src/client/safe-image.js +91 -91
  54. package/src/client/shared-cookie.js +225 -225
  55. package/src/client/store.js +36 -36
  56. package/src/client/swap.js +188 -188
  57. package/src/compile/codegen.js +336 -336
  58. package/src/compile/compile-all.js +149 -149
  59. package/src/compile/errors.js +66 -66
  60. package/src/compile/expr.js +409 -409
  61. package/src/compile/index.js +17 -17
  62. package/src/compile/parse.js +541 -541
  63. package/src/compile/resolve.js +211 -211
  64. package/src/compile/scan-exports.js +51 -51
  65. package/src/config/defaults.js +17 -1
  66. package/src/config/index.js +13 -0
  67. package/src/config/pattern.js +107 -107
  68. package/src/generate.mjs +163 -163
  69. package/src/http/control-flow.js +71 -71
  70. package/src/http/cookies-entry.js +21 -21
  71. package/src/http/cookies.js +277 -277
  72. package/src/http/request-cache.js +46 -46
  73. package/src/http/request-context.js +165 -165
  74. package/src/http/shared-cookie.js +178 -178
  75. package/src/index.js +101 -101
  76. package/src/init.mjs +230 -230
  77. package/src/migrate/apply.mjs +262 -262
  78. package/src/migrate/babel.mjs +79 -79
  79. package/src/migrate/classify.mjs +155 -155
  80. package/src/migrate/config.mjs +126 -126
  81. package/src/migrate/fs-walk.mjs +191 -191
  82. package/src/migrate/parse.mjs +26 -26
  83. package/src/migrate/scan.mjs +177 -177
  84. package/src/migrate/transform/expr-source.mjs +168 -168
  85. package/src/migrate/transform/island.mjs +67 -67
  86. package/src/migrate/transform/jsx-to-component.mjs +302 -302
  87. package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
  88. package/src/migrate/transform/page-split.mjs +435 -435
  89. package/src/migrate/write.mjs +81 -81
  90. package/src/migrate.mjs +171 -171
  91. package/src/runtime/alias-hooks.mjs +119 -119
  92. package/src/runtime/register.mjs +4 -4
  93. package/src/server/admin/actions.js +229 -229
  94. package/src/server/admin/auth.js +125 -125
  95. package/src/server/admin/event-log.js +151 -151
  96. package/src/server/admin/gate.js +209 -209
  97. package/src/server/admin/inventory.js +188 -188
  98. package/src/server/admin/mount.js +56 -56
  99. package/src/server/admin/router.js +216 -216
  100. package/src/server/admin/snapshot.js +241 -241
  101. package/src/server/assets.js +147 -147
  102. package/src/server/auth/handoff.js +309 -309
  103. package/src/server/cache-blob.js +70 -0
  104. package/src/server/cache-deps.js +42 -42
  105. package/src/server/cache-vary.js +113 -113
  106. package/src/server/cloudflare.js +607 -607
  107. package/src/server/create-app.js +366 -366
  108. package/src/server/data-cache.js +553 -462
  109. package/src/server/dev/report.js +485 -485
  110. package/src/server/dev/socket.js +170 -170
  111. package/src/server/dev/version-check.mjs +139 -139
  112. package/src/server/disk-cache.js +233 -0
  113. package/src/server/ejs-adapter.js +59 -59
  114. package/src/server/html-cache.js +1196 -1122
  115. package/src/server/image-optimizer.js +500 -407
  116. package/src/server/logs/access-middleware.js +66 -66
  117. package/src/server/logs/file-sink.js +193 -66
  118. package/src/server/logs/pipeline.js +165 -158
  119. package/src/server/logs/s3-put.js +214 -214
  120. package/src/server/logs/s3-sink.js +112 -112
  121. package/src/server/metadata.js +102 -102
  122. package/src/server/middleware/compression.js +205 -205
  123. package/src/server/middleware/csrf.js +134 -134
  124. package/src/server/middleware/dev-gate.js +75 -75
  125. package/src/server/middleware/headers.js +37 -37
  126. package/src/server/middleware/redirects.js +32 -32
  127. package/src/server/middleware/robots-txt.js +341 -341
  128. package/src/server/middleware/static-precompressed.js +121 -100
  129. package/src/server/middleware/trailing-slash.js +53 -53
  130. package/src/server/middleware/upstream-proxy.js +141 -141
  131. package/src/server/og-image.js +356 -356
  132. package/src/server/port-guard.js +255 -255
  133. package/src/server/prewarm.js +1082 -1058
  134. package/src/server/redis.js +588 -569
  135. package/src/server/render.js +4 -4
  136. package/src/server/router.js +157 -157
  137. package/src/server/status-page.js +265 -265
  138. package/src/server/upstream-limiter.js +376 -376
  139. package/src/server/upstream-tracking.js +166 -166
  140. package/src/shared/cookie-domain.js +66 -66
  141. package/src/start.mjs +22 -22
  142. package/src/templates/layout.ejs +30 -30
  143. package/src/templates/layout.jsk +30 -30
  144. package/src/version.mjs +31 -31
  145. package/src/views/components/loader.js +101 -101
  146. package/src/views/helpers/html.js +102 -102
  147. package/src/views/helpers/tags.js +375 -375
  148. package/types/config/defaults.d.ts +15 -1
  149. package/types/config/index.d.ts +8 -0
  150. package/types/server/cache-blob.d.ts +13 -0
  151. package/types/server/data-cache.d.ts +9 -0
  152. package/types/server/disk-cache.d.ts +36 -0
  153. package/types/server/html-cache.d.ts +26 -3
  154. package/types/server/logs/file-sink.d.ts +16 -5
  155. package/types/server/redis.d.ts +2 -1
@@ -1,66 +1,66 @@
1
- /**
2
- * HTTP access log middleware.
3
- *
4
- * Admin ve prewarm yolları atlanır. Kayıt `log.http` ile stdout'a (console
5
- * açıksa) ve `acceptLogEntry` ile sink'lere gider. `emitHttp` kapalı olduğu
6
- * için `log.http` abonelere ikinci kez yazmaz.
7
- */
8
- import * as log from "../../log.mjs";
9
- import { getConfig } from "../../config/index.js";
10
- import { acceptLogEntry } from "./pipeline.js";
11
-
12
- /**
13
- * @param {{ basePath?: string }} [options]
14
- * @returns {import('express').RequestHandler}
15
- */
16
- export function accessLogMiddleware(options = {}) {
17
- const config = getConfig();
18
- const brand = config.brand;
19
- const basePath =
20
- options.basePath ??
21
- (config.admin.enabled ? config.admin.basePath : null);
22
-
23
- /** @type {import('express').RequestHandler} */
24
- return (req, res, next) => {
25
- const pathname = req.path || "";
26
- if (basePath && (pathname === basePath || pathname.startsWith(`${basePath}/`))) {
27
- return next();
28
- }
29
-
30
- if (req.headers["user-agent"] === brand.prewarmUserAgent) {
31
- return next();
32
- }
33
-
34
- const started = process.hrtime.bigint();
35
-
36
- res.on("finish", () => {
37
- const ms = Number(process.hrtime.bigint() - started) / 1e6;
38
- const cache = /** @type {string | null} */ (
39
- res.getHeader(brand.cacheHeader) ?? null
40
- );
41
-
42
- const info = {
43
- method: req.method,
44
- url: req.originalUrl,
45
- status: res.statusCode,
46
- ms,
47
- cache,
48
- };
49
-
50
- // Stdout (console açıksa). emitHttp kapalı → subscribe çiftlemez.
51
- log.http(info);
52
-
53
- acceptLogEntry({
54
- kind: "http",
55
- method: info.method,
56
- url: info.url,
57
- path: req.path,
58
- status: info.status,
59
- ms: info.ms,
60
- cache: info.cache,
61
- });
62
- });
63
-
64
- next();
65
- };
66
- }
1
+ /**
2
+ * HTTP access log middleware.
3
+ *
4
+ * Admin ve prewarm yolları atlanır. Kayıt `log.http` ile stdout'a (console
5
+ * açıksa) ve `acceptLogEntry` ile sink'lere gider. `emitHttp` kapalı olduğu
6
+ * için `log.http` abonelere ikinci kez yazmaz.
7
+ */
8
+ import * as log from "../../log.mjs";
9
+ import { getConfig } from "../../config/index.js";
10
+ import { acceptLogEntry } from "./pipeline.js";
11
+
12
+ /**
13
+ * @param {{ basePath?: string }} [options]
14
+ * @returns {import('express').RequestHandler}
15
+ */
16
+ export function accessLogMiddleware(options = {}) {
17
+ const config = getConfig();
18
+ const brand = config.brand;
19
+ const basePath =
20
+ options.basePath ??
21
+ (config.admin.enabled ? config.admin.basePath : null);
22
+
23
+ /** @type {import('express').RequestHandler} */
24
+ return (req, res, next) => {
25
+ const pathname = req.path || "";
26
+ if (basePath && (pathname === basePath || pathname.startsWith(`${basePath}/`))) {
27
+ return next();
28
+ }
29
+
30
+ if (req.headers["user-agent"] === brand.prewarmUserAgent) {
31
+ return next();
32
+ }
33
+
34
+ const started = process.hrtime.bigint();
35
+
36
+ res.on("finish", () => {
37
+ const ms = Number(process.hrtime.bigint() - started) / 1e6;
38
+ const cache = /** @type {string | null} */ (
39
+ res.getHeader(brand.cacheHeader) ?? null
40
+ );
41
+
42
+ const info = {
43
+ method: req.method,
44
+ url: req.originalUrl,
45
+ status: res.statusCode,
46
+ ms,
47
+ cache,
48
+ };
49
+
50
+ // Stdout (console açıksa). emitHttp kapalı → subscribe çiftlemez.
51
+ log.http(info);
52
+
53
+ acceptLogEntry({
54
+ kind: "http",
55
+ method: info.method,
56
+ url: info.url,
57
+ path: req.path,
58
+ status: info.status,
59
+ ms: info.ms,
60
+ cache: info.cache,
61
+ });
62
+ });
63
+
64
+ next();
65
+ };
66
+ }
@@ -1,66 +1,193 @@
1
- /**
2
- * Günlük rotasyonlu NDJSON dosya sink'i.
3
- *
4
- * Yol `path.resolve(root, dir)` ile bulunur — `getConfig().dirs` şişirilmez;
5
- * log dizini opsiyonel bir yüzey ve çoğu kurulumda hiç açılmaz.
6
- */
7
- import fs from "node:fs/promises";
8
- import path from "node:path";
9
-
10
- /**
11
- * @typedef {{ write: (entry: Record<string, unknown>) => Promise<void>,
12
- * flush: () => Promise<void>, close: () => Promise<void> }} LogSink
13
- */
14
-
15
- /**
16
- * @param {{ root: string, dir: string }} options
17
- * @returns {LogSink}
18
- */
19
- export function createFileSink(options) {
20
- const baseDir = path.resolve(options.root, options.dir);
21
- /** @type {string | null} */
22
- let currentDay = null;
23
- /** @type {string | null} */
24
- let currentPath = null;
25
- let ready = false;
26
-
27
- /** @returns {string} */
28
- function dayStamp(date = new Date()) {
29
- const y = date.getUTCFullYear();
30
- const m = String(date.getUTCMonth() + 1).padStart(2, "0");
31
- const d = String(date.getUTCDate()).padStart(2, "0");
32
- return `${y}-${m}-${d}`;
33
- }
34
-
35
- /**
36
- * @param {string} day
37
- * @returns {Promise<string>}
38
- */
39
- async function ensureFile(day) {
40
- if (!ready) {
41
- await fs.mkdir(baseDir, { recursive: true });
42
- ready = true;
43
- }
44
- if (currentDay === day && currentPath) return currentPath;
45
- currentDay = day;
46
- currentPath = path.join(baseDir, `jskelet-${day}.log`);
47
- return currentPath;
48
- }
49
-
50
- return {
51
- async write(entry) {
52
- const day = dayStamp(
53
- typeof entry.at === "number" ? new Date(entry.at) : new Date(),
54
- );
55
- const file = await ensureFile(day);
56
- await fs.appendFile(file, `${JSON.stringify(entry)}\n`, "utf8");
57
- },
58
- async flush() {
59
- // appendFile senkron flush eder; ekstra iş yok.
60
- },
61
- async close() {
62
- currentDay = null;
63
- currentPath = null;
64
- },
65
- };
66
- }
1
+ /**
2
+ * Kısa ömürlü dosya logu.
3
+ *
4
+ * Satırlar ~1 sn veya 32 satırda bir zstd NDJSON parçası olur. Diskte en
5
+ * fazla 5 dakika kalır; süresi dolan en eski parça silinir. `drainLog`
6
+ * her mühürlenen parçayı alır — hook hata verse de site düşmez, dosya
7
+ * süresi dolana kadar durur.
8
+ *
9
+ * Yol `path.resolve(root, dir)` ile bulunur. `getConfig().dirs` şişirilmez;
10
+ * log dizini opsiyoneldir ve çoğu kurulumda hiç açılmaz.
11
+ */
12
+ import fs from "node:fs/promises";
13
+ import path from "node:path";
14
+ import zlib from "node:zlib";
15
+
16
+ /**
17
+ * @typedef {{ body: Buffer, encoding: "zstd", bytes: number, lines: number,
18
+ * at: number }} LogChunk
19
+ * @typedef {{ write: (entry: Record<string, unknown>) => Promise<void>,
20
+ * flush: () => Promise<void>, close: () => Promise<void> }} LogSink
21
+ * @typedef {(chunk: LogChunk) => void | Promise<void>} DrainLog
22
+ */
23
+
24
+ /** Satır satır yazmak yerine kısa tampon. */
25
+ const FLUSH_INTERVAL_MS = 1000;
26
+ const FLUSH_MAX_LINES = 32;
27
+
28
+ /** Diskteki parçaların ömrü. Config yükseltemez. */
29
+ export const FILE_LOG_RETENTION_MS = 5 * 60 * 1000;
30
+
31
+ /**
32
+ * @param {{ root: string, dir: string, persist?: boolean,
33
+ * retentionMs?: number, drainLog?: DrainLog | null }} options
34
+ * `persist: false` diske yazmaz; yalnız `drainLog` çağrılır.
35
+ * @returns {LogSink}
36
+ */
37
+ export function createFileSink(options) {
38
+ const persist = options.persist !== false;
39
+ const retentionMs = options.retentionMs ?? FILE_LOG_RETENTION_MS;
40
+ const drainLog = typeof options.drainLog === "function" ? options.drainLog : null;
41
+ const baseDir = path.resolve(options.root, options.dir);
42
+ /** @type {string[]} */
43
+ let pending = [];
44
+ /** @type {ReturnType<typeof setTimeout> | null} */
45
+ let timer = null;
46
+ /** @type {Promise<void>} */
47
+ let chain = Promise.resolve();
48
+ let seq = 0;
49
+ let ready = false;
50
+ let drainWarned = false;
51
+
52
+ /**
53
+ * @param {string} message
54
+ * @param {unknown} [error]
55
+ */
56
+ function warnDrain(message, error) {
57
+ if (drainWarned) return;
58
+ drainWarned = true;
59
+ console.warn(`[logs] ${message}`, error ?? "");
60
+ }
61
+
62
+ /**
63
+ * @returns {Promise<void>}
64
+ */
65
+ async function ensureDir() {
66
+ if (ready) return;
67
+ await fs.mkdir(baseDir, { recursive: true });
68
+ ready = true;
69
+ }
70
+
71
+ /**
72
+ * Süresi dolmuş `jskelet-*` parçalarını eskiden yeniye siler. Başka
73
+ * dosyalara dokunulmaz. Eski günlük `.log` dosyaları da bu ada uyar.
74
+ *
75
+ * @returns {Promise<void>}
76
+ */
77
+ async function prune() {
78
+ if (!persist) return;
79
+ let names;
80
+ try {
81
+ names = await fs.readdir(baseDir);
82
+ } catch {
83
+ return;
84
+ }
85
+
86
+ const now = Date.now();
87
+ /** @type {{ file: string, mtimeMs: number }[]} */
88
+ const doomed = [];
89
+
90
+ for (const name of names) {
91
+ if (!name.startsWith("jskelet-")) continue;
92
+ const file = path.join(baseDir, name);
93
+ try {
94
+ const stat = await fs.stat(file);
95
+ if (!stat.isFile()) continue;
96
+ if (now - stat.mtimeMs < retentionMs) continue;
97
+ doomed.push({ file, mtimeMs: stat.mtimeMs });
98
+ } catch {
99
+ // Silinmiş veya okunamayan girdi turu bozmasın.
100
+ }
101
+ }
102
+
103
+ doomed.sort((a, b) => a.mtimeMs - b.mtimeMs);
104
+ for (const item of doomed) {
105
+ await fs.rm(item.file, { force: true });
106
+ }
107
+ }
108
+
109
+ /**
110
+ * @param {string} text
111
+ * @param {number} lines
112
+ * @returns {Promise<void>}
113
+ */
114
+ async function seal(text, lines) {
115
+ const body = zlib.zstdCompressSync(Buffer.from(text, "utf8"));
116
+ const at = Date.now();
117
+
118
+ if (persist) {
119
+ await ensureDir();
120
+ seq += 1;
121
+ const file = path.join(baseDir, `jskelet-${at}-${seq}.ndjson.zst`);
122
+ await fs.writeFile(file, body);
123
+ await prune();
124
+ }
125
+
126
+ if (!drainLog) return;
127
+
128
+ try {
129
+ await drainLog({
130
+ body,
131
+ encoding: "zstd",
132
+ bytes: body.length,
133
+ lines,
134
+ at,
135
+ });
136
+ } catch (error) {
137
+ warnDrain(
138
+ persist
139
+ ? "drainLog failed; the chunk stays on disk until it expires"
140
+ : "drainLog failed; the chunk was not stored",
141
+ error,
142
+ );
143
+ }
144
+ }
145
+
146
+ /**
147
+ * @returns {Promise<void>}
148
+ */
149
+ async function flushPending() {
150
+ if (timer) {
151
+ clearTimeout(timer);
152
+ timer = null;
153
+ }
154
+ if (!pending.length) return;
155
+
156
+ const lines = pending;
157
+ pending = [];
158
+ await seal(lines.join(""), lines.length);
159
+ }
160
+
161
+ /** @returns {void} */
162
+ function schedule() {
163
+ if (timer || !pending.length) return;
164
+ timer = setTimeout(() => {
165
+ timer = null;
166
+ chain = chain.then(() => flushPending());
167
+ }, FLUSH_INTERVAL_MS);
168
+ timer.unref?.();
169
+ }
170
+
171
+ return {
172
+ async write(entry) {
173
+ pending.push(`${JSON.stringify(entry)}\n`);
174
+
175
+ if (pending.length >= FLUSH_MAX_LINES) {
176
+ chain = chain.then(() => flushPending());
177
+ await chain;
178
+ return;
179
+ }
180
+
181
+ schedule();
182
+ },
183
+ async flush() {
184
+ chain = chain.then(() => flushPending());
185
+ await chain;
186
+ },
187
+ async close() {
188
+ chain = chain.then(() => flushPending());
189
+ await chain;
190
+ if (persist) await prune();
191
+ },
192
+ };
193
+ }