jskelet 0.2.4 → 0.2.5

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 (91) hide show
  1. package/AGENTS.md +132 -132
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +21 -21
  4. package/bin/jskelet.mjs +103 -103
  5. package/docs/01-baslangic.md +285 -285
  6. package/docs/02-mimari.md +287 -287
  7. package/docs/03-routing.md +480 -480
  8. package/docs/04-render-ve-sablonlar.md +490 -490
  9. package/docs/05-islands.md +482 -482
  10. package/docs/06-cache.md +1209 -1209
  11. package/docs/08-build.md +366 -366
  12. package/docs/09-dev-araclari.md +335 -335
  13. package/docs/10-dagitim.md +329 -329
  14. package/docs/12-panel-ve-oturum.md +384 -384
  15. package/docs/README.md +105 -105
  16. package/docs/en/01-getting-started.md +292 -292
  17. package/docs/en/02-architecture.md +305 -305
  18. package/docs/en/03-routing.md +497 -497
  19. package/docs/en/04-rendering.md +504 -504
  20. package/docs/en/05-islands.md +492 -492
  21. package/docs/en/06-caching.md +1239 -1239
  22. package/docs/en/07-configuration.md +986 -986
  23. package/docs/en/08-build.md +383 -383
  24. package/docs/en/09-dev-tools.md +342 -342
  25. package/docs/en/10-deployment.md +332 -332
  26. package/docs/en/11-migration.md +359 -359
  27. package/docs/en/12-dashboards-and-sessions.md +392 -392
  28. package/docs/en/README.md +112 -112
  29. package/package.json +102 -102
  30. package/src/build/ensure-build.mjs +15 -15
  31. package/src/build/paths.mjs +143 -143
  32. package/src/build/resolve-peer.mjs +36 -36
  33. package/src/build/tasks/client.mjs +268 -268
  34. package/src/build/tasks/css.mjs +124 -124
  35. package/src/build/tasks/fonts.mjs +146 -146
  36. package/src/build/tasks/icons.mjs +224 -224
  37. package/src/build/tasks/images.mjs +244 -244
  38. package/src/build/tasks/precompress.mjs +78 -78
  39. package/src/client/cache-panel/i18n.js +670 -670
  40. package/src/client/cache-panel/login.html +74 -74
  41. package/src/client/cache-panel/panel.css +756 -756
  42. package/src/client/cache-panel/panel.html +308 -308
  43. package/src/client/cache-panel/panel.js +915 -915
  44. package/src/client/devtools/report.html +185 -185
  45. package/src/client/devtools/report.js +725 -725
  46. package/src/client/dom.js +95 -95
  47. package/src/client/form.js +192 -192
  48. package/src/client/index.js +35 -35
  49. package/src/client/registry.js +297 -297
  50. package/src/client/safe-image.js +91 -91
  51. package/src/client/store.js +36 -36
  52. package/src/client/swap.js +188 -188
  53. package/src/config/pattern.js +107 -107
  54. package/src/http/control-flow.js +71 -71
  55. package/src/http/cookies.js +257 -257
  56. package/src/http/request-cache.js +46 -46
  57. package/src/http/request-context.js +162 -162
  58. package/src/index.js +83 -83
  59. package/src/init.mjs +221 -221
  60. package/src/runtime/alias-hooks.mjs +119 -119
  61. package/src/runtime/register.mjs +4 -4
  62. package/src/server/assets.js +147 -147
  63. package/src/server/cache-deps.js +42 -42
  64. package/src/server/cache-panel.js +759 -759
  65. package/src/server/cloudflare.js +607 -595
  66. package/src/server/create-app.js +291 -291
  67. package/src/server/data-cache.js +462 -462
  68. package/src/server/dev/report.js +369 -369
  69. package/src/server/dev/socket.js +170 -170
  70. package/src/server/dev/version-check.mjs +139 -139
  71. package/src/server/html-cache.js +817 -817
  72. package/src/server/metadata.js +102 -102
  73. package/src/server/middleware/compression.js +205 -205
  74. package/src/server/middleware/csrf.js +134 -134
  75. package/src/server/middleware/dev-gate.js +62 -62
  76. package/src/server/middleware/headers.js +37 -37
  77. package/src/server/middleware/redirects.js +32 -32
  78. package/src/server/middleware/static-precompressed.js +100 -100
  79. package/src/server/middleware/upstream-proxy.js +141 -141
  80. package/src/server/prewarm.js +601 -601
  81. package/src/server/redis.js +569 -569
  82. package/src/server/router.js +128 -128
  83. package/src/server/status-page.js +164 -164
  84. package/src/server/upstream-limiter.js +376 -376
  85. package/src/server/upstream-tracking.js +166 -166
  86. package/src/start.mjs +7 -7
  87. package/src/templates/layout.ejs +44 -44
  88. package/src/version.mjs +31 -31
  89. package/src/views/components/loader.js +85 -85
  90. package/src/views/helpers/html.js +102 -102
  91. package/src/views/helpers/tags.js +245 -245
@@ -1,257 +1,257 @@
1
- /**
2
- * Cookie okuma/yazma ve HMAC ile imzalama.
3
- *
4
- * Neden framework'te: kişiye özel her sayfa bir oturum cookie'sine dayanıyor
5
- * ve bunu elle yazan her proje aynı üç hatayı tekrar ediyor — `HttpOnly`
6
- * unutmak, imzasız değere güvenmek, karşılaştırmayı `===` ile yapmak.
7
- * Burada varsayılanlar güvenli tarafta ve imza doğrulaması sabit zamanlı.
8
- *
9
- * Framework **kimlik sağlamaz**: oturumun içinde ne olduğu, ne kadar
10
- * yaşadığı ve kimin verdiği uygulamanın kararı. Buradaki yüzey yalnızca
11
- * "bu değeri ben yazdım, kurcalanmamış" garantisini veriyor.
12
- *
13
- * Bağımlılık eklenmez; `node:crypto` yeterli.
14
- */
15
- import crypto from "node:crypto";
16
- import process from "node:process";
17
- import { getConfig } from "../config/index.js";
18
- import { markTainted } from "./request-context.js";
19
-
20
- /** Ayrıştırılmış cookie'ler istek başına bir kez hesaplanır. */
21
- const PARSED = Symbol("jskelet.cookies");
22
-
23
- /**
24
- * @param {string} value
25
- * @returns {string}
26
- */
27
- function base64url(value) {
28
- return Buffer.from(value, "utf8").toString("base64url");
29
- }
30
-
31
- /**
32
- * @param {string} value
33
- * @returns {string | null}
34
- */
35
- function fromBase64url(value) {
36
- try {
37
- return Buffer.from(value, "base64url").toString("utf8");
38
- } catch {
39
- return null;
40
- }
41
- }
42
-
43
- /**
44
- * İmza sırrı. `security.cookieSecret` ya da `JSKELET_SECRET`.
45
- *
46
- * Yokluğunda imzasız cookie yazmak en kötü sonuç olurdu: uygulama kendini
47
- * güvende sanar, değer kurcalanabilir. Bu yüzden imzalı API sır olmadan
48
- * hata verir — config hatasının siteyi düşürmemesi kuralı burada geçmez,
49
- * çünkü sessiz alternatif bir güvenlik açığı.
50
- *
51
- * @returns {string}
52
- */
53
- function getSecret() {
54
- /** @type {string | null} */
55
- let configured = null;
56
-
57
- try {
58
- configured = getConfig().security.cookieSecret;
59
- } catch {
60
- // Config yüklenmemiş olabilir (script, test); env yine de geçerli.
61
- configured = null;
62
- }
63
-
64
- const secret = configured ?? process.env.JSKELET_SECRET ?? null;
65
-
66
- if (!secret) {
67
- throw new Error(
68
- "[cookies] a secret is required for signed cookies. Set " +
69
- "`security.cookieSecret` in `jskelet.config.mjs` or the JSKELET_SECRET environment variable.",
70
- );
71
- }
72
-
73
- return secret;
74
- }
75
-
76
- /**
77
- * @param {string} value
78
- * @returns {string}
79
- */
80
- function sign(value) {
81
- return crypto.createHmac("sha256", getSecret()).update(value).digest("base64url");
82
- }
83
-
84
- /**
85
- * Sabit zamanlı karşılaştırma: imza doğrulamasında erken çıkış, saldırganın
86
- * baytları tek tek tahmin etmesine kapı aralar.
87
- *
88
- * @param {string} a
89
- * @param {string} b
90
- * @returns {boolean}
91
- */
92
- export function safeEqual(a, b) {
93
- const left = Buffer.from(String(a));
94
- const right = Buffer.from(String(b));
95
- if (left.length !== right.length) return false;
96
- return crypto.timingSafeEqual(left, right);
97
- }
98
-
99
- /**
100
- * `Cookie` başlığını ayrıştırır.
101
- *
102
- * Okuma çıktının kullanıcıya bağlı olduğunu bildirir: bu sayfa artık public
103
- * HTML cache'ine yazılamaz.
104
- *
105
- * @param {import('http').IncomingMessage} req
106
- * @returns {Record<string, string>}
107
- */
108
- export function parseCookies(req) {
109
- markTainted("parseCookies(req)");
110
-
111
- const cached = /** @type {any} */ (req)[PARSED];
112
- if (cached) return cached;
113
-
114
- /** @type {Record<string, string>} */
115
- const out = {};
116
- const header = req.headers?.cookie;
117
-
118
- if (header) {
119
- for (const part of header.split(";")) {
120
- const index = part.indexOf("=");
121
- if (index === -1) continue;
122
-
123
- const name = part.slice(0, index).trim();
124
- if (!name) continue;
125
-
126
- try {
127
- out[name] = decodeURIComponent(part.slice(index + 1).trim());
128
- } catch {
129
- // Bozuk yüzde kodlaması tüm başlığı çöpe atmamalı.
130
- out[name] = part.slice(index + 1).trim();
131
- }
132
- }
133
- }
134
-
135
- /** @type {any} */ (req)[PARSED] = out;
136
- return out;
137
- }
138
-
139
- /**
140
- * @typedef {object} CookieOptions
141
- * @property {string} [path] Varsayılan `/`.
142
- * @property {string} [domain]
143
- * @property {number} [maxAge] Saniye.
144
- * @property {Date} [expires]
145
- * @property {boolean} [httpOnly] Varsayılan `true`.
146
- * @property {boolean} [secure] Varsayılan: development dışında `true`.
147
- * @property {"Strict" | "Lax" | "None"} [sameSite] Varsayılan `Lax`.
148
- */
149
-
150
- /**
151
- * Varsayılanlar bilinçli olarak kısıtlayıcı: `HttpOnly` ile JS okuyamaz,
152
- * `SameSite=Lax` ile çapraz site POST'larında gönderilmez (CSRF'nin büyük
153
- * kısmını kapatan tek satır), `Secure` üretimde açık.
154
- *
155
- * @param {string} name
156
- * @param {string} value
157
- * @param {CookieOptions} [options]
158
- * @returns {string}
159
- */
160
- export function serializeCookie(name, value, options = {}) {
161
- const parts = [`${name}=${encodeURIComponent(value)}`];
162
-
163
- parts.push(`Path=${options.path ?? "/"}`);
164
- if (options.domain) parts.push(`Domain=${options.domain}`);
165
- if (options.maxAge !== undefined) parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
166
- if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
167
- if (options.httpOnly !== false) parts.push("HttpOnly");
168
- if (options.secure ?? process.env.NODE_ENV !== "development") parts.push("Secure");
169
- parts.push(`SameSite=${options.sameSite ?? "Lax"}`);
170
-
171
- return parts.join("; ");
172
- }
173
-
174
- /**
175
- * @param {import('http').ServerResponse} res
176
- * @param {string} name
177
- * @param {string} value
178
- * @param {CookieOptions} [options]
179
- */
180
- export function setCookie(res, name, value, options = {}) {
181
- const existing = res.getHeader("Set-Cookie");
182
- const serialized = serializeCookie(name, value, options);
183
-
184
- /** @type {string[]} */
185
- const all = existing
186
- ? Array.isArray(existing)
187
- ? [...existing.map(String)]
188
- : [String(existing)]
189
- : [];
190
-
191
- all.push(serialized);
192
- res.setHeader("Set-Cookie", all);
193
- }
194
-
195
- /**
196
- * @param {import('http').ServerResponse} res
197
- * @param {string} name
198
- * @param {CookieOptions} [options]
199
- */
200
- export function clearCookie(res, name, options = {}) {
201
- setCookie(res, name, "", { ...options, maxAge: 0, expires: new Date(0) });
202
- }
203
-
204
- /**
205
- * İmzalı cookie yazar. Değer okunabilir kalır (şifreleme değil, imza);
206
- * gizli kalması gereken veriyi cookie'ye koymayın, kimliğini koyun.
207
- *
208
- * @param {import('http').ServerResponse} res
209
- * @param {string} name
210
- * @param {string} value
211
- * @param {CookieOptions} [options]
212
- */
213
- export function setSignedCookie(res, name, value, options = {}) {
214
- const encoded = base64url(value);
215
- setCookie(res, name, `${encoded}.${sign(encoded)}`, options);
216
- }
217
-
218
- /**
219
- * İmzalı cookie okur. İmza uymuyorsa `null` — bozuk imza, yok sayılmalı,
220
- * "belki geçerlidir" diye kullanılmamalı.
221
- *
222
- * @param {import('http').IncomingMessage} req
223
- * @param {string} name
224
- * @returns {string | null}
225
- */
226
- export function getSignedCookie(req, name) {
227
- const raw = parseCookies(req)[name];
228
- if (!raw) return null;
229
-
230
- const index = raw.lastIndexOf(".");
231
- if (index <= 0) return null;
232
-
233
- const encoded = raw.slice(0, index);
234
- const signature = raw.slice(index + 1);
235
-
236
- let expected;
237
- try {
238
- expected = sign(encoded);
239
- } catch {
240
- // Sır yoksa imzalı okuma sessizce başarısız olur: sunucu ayakta kalır
241
- // ama hiçbir oturum geçerli sayılmaz.
242
- return null;
243
- }
244
-
245
- if (!safeEqual(signature, expected)) return null;
246
- return fromBase64url(encoded);
247
- }
248
-
249
- /**
250
- * Kriptografik rastgele token. CSRF token'ı ve oturum kimliği için.
251
- *
252
- * @param {number} [bytes]
253
- * @returns {string}
254
- */
255
- export function randomToken(bytes = 32) {
256
- return crypto.randomBytes(bytes).toString("base64url");
257
- }
1
+ /**
2
+ * Cookie okuma/yazma ve HMAC ile imzalama.
3
+ *
4
+ * Neden framework'te: kişiye özel her sayfa bir oturum cookie'sine dayanıyor
5
+ * ve bunu elle yazan her proje aynı üç hatayı tekrar ediyor — `HttpOnly`
6
+ * unutmak, imzasız değere güvenmek, karşılaştırmayı `===` ile yapmak.
7
+ * Burada varsayılanlar güvenli tarafta ve imza doğrulaması sabit zamanlı.
8
+ *
9
+ * Framework **kimlik sağlamaz**: oturumun içinde ne olduğu, ne kadar
10
+ * yaşadığı ve kimin verdiği uygulamanın kararı. Buradaki yüzey yalnızca
11
+ * "bu değeri ben yazdım, kurcalanmamış" garantisini veriyor.
12
+ *
13
+ * Bağımlılık eklenmez; `node:crypto` yeterli.
14
+ */
15
+ import crypto from "node:crypto";
16
+ import process from "node:process";
17
+ import { getConfig } from "../config/index.js";
18
+ import { markTainted } from "./request-context.js";
19
+
20
+ /** Ayrıştırılmış cookie'ler istek başına bir kez hesaplanır. */
21
+ const PARSED = Symbol("jskelet.cookies");
22
+
23
+ /**
24
+ * @param {string} value
25
+ * @returns {string}
26
+ */
27
+ function base64url(value) {
28
+ return Buffer.from(value, "utf8").toString("base64url");
29
+ }
30
+
31
+ /**
32
+ * @param {string} value
33
+ * @returns {string | null}
34
+ */
35
+ function fromBase64url(value) {
36
+ try {
37
+ return Buffer.from(value, "base64url").toString("utf8");
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * İmza sırrı. `security.cookieSecret` ya da `JSKELET_SECRET`.
45
+ *
46
+ * Yokluğunda imzasız cookie yazmak en kötü sonuç olurdu: uygulama kendini
47
+ * güvende sanar, değer kurcalanabilir. Bu yüzden imzalı API sır olmadan
48
+ * hata verir — config hatasının siteyi düşürmemesi kuralı burada geçmez,
49
+ * çünkü sessiz alternatif bir güvenlik açığı.
50
+ *
51
+ * @returns {string}
52
+ */
53
+ function getSecret() {
54
+ /** @type {string | null} */
55
+ let configured = null;
56
+
57
+ try {
58
+ configured = getConfig().security.cookieSecret;
59
+ } catch {
60
+ // Config yüklenmemiş olabilir (script, test); env yine de geçerli.
61
+ configured = null;
62
+ }
63
+
64
+ const secret = configured ?? process.env.JSKELET_SECRET ?? null;
65
+
66
+ if (!secret) {
67
+ throw new Error(
68
+ "[cookies] a secret is required for signed cookies. Set " +
69
+ "`security.cookieSecret` in `jskelet.config.mjs` or the JSKELET_SECRET environment variable.",
70
+ );
71
+ }
72
+
73
+ return secret;
74
+ }
75
+
76
+ /**
77
+ * @param {string} value
78
+ * @returns {string}
79
+ */
80
+ function sign(value) {
81
+ return crypto.createHmac("sha256", getSecret()).update(value).digest("base64url");
82
+ }
83
+
84
+ /**
85
+ * Sabit zamanlı karşılaştırma: imza doğrulamasında erken çıkış, saldırganın
86
+ * baytları tek tek tahmin etmesine kapı aralar.
87
+ *
88
+ * @param {string} a
89
+ * @param {string} b
90
+ * @returns {boolean}
91
+ */
92
+ export function safeEqual(a, b) {
93
+ const left = Buffer.from(String(a));
94
+ const right = Buffer.from(String(b));
95
+ if (left.length !== right.length) return false;
96
+ return crypto.timingSafeEqual(left, right);
97
+ }
98
+
99
+ /**
100
+ * `Cookie` başlığını ayrıştırır.
101
+ *
102
+ * Okuma çıktının kullanıcıya bağlı olduğunu bildirir: bu sayfa artık public
103
+ * HTML cache'ine yazılamaz.
104
+ *
105
+ * @param {import('http').IncomingMessage} req
106
+ * @returns {Record<string, string>}
107
+ */
108
+ export function parseCookies(req) {
109
+ markTainted("parseCookies(req)");
110
+
111
+ const cached = /** @type {any} */ (req)[PARSED];
112
+ if (cached) return cached;
113
+
114
+ /** @type {Record<string, string>} */
115
+ const out = {};
116
+ const header = req.headers?.cookie;
117
+
118
+ if (header) {
119
+ for (const part of header.split(";")) {
120
+ const index = part.indexOf("=");
121
+ if (index === -1) continue;
122
+
123
+ const name = part.slice(0, index).trim();
124
+ if (!name) continue;
125
+
126
+ try {
127
+ out[name] = decodeURIComponent(part.slice(index + 1).trim());
128
+ } catch {
129
+ // Bozuk yüzde kodlaması tüm başlığı çöpe atmamalı.
130
+ out[name] = part.slice(index + 1).trim();
131
+ }
132
+ }
133
+ }
134
+
135
+ /** @type {any} */ (req)[PARSED] = out;
136
+ return out;
137
+ }
138
+
139
+ /**
140
+ * @typedef {object} CookieOptions
141
+ * @property {string} [path] Varsayılan `/`.
142
+ * @property {string} [domain]
143
+ * @property {number} [maxAge] Saniye.
144
+ * @property {Date} [expires]
145
+ * @property {boolean} [httpOnly] Varsayılan `true`.
146
+ * @property {boolean} [secure] Varsayılan: development dışında `true`.
147
+ * @property {"Strict" | "Lax" | "None"} [sameSite] Varsayılan `Lax`.
148
+ */
149
+
150
+ /**
151
+ * Varsayılanlar bilinçli olarak kısıtlayıcı: `HttpOnly` ile JS okuyamaz,
152
+ * `SameSite=Lax` ile çapraz site POST'larında gönderilmez (CSRF'nin büyük
153
+ * kısmını kapatan tek satır), `Secure` üretimde açık.
154
+ *
155
+ * @param {string} name
156
+ * @param {string} value
157
+ * @param {CookieOptions} [options]
158
+ * @returns {string}
159
+ */
160
+ export function serializeCookie(name, value, options = {}) {
161
+ const parts = [`${name}=${encodeURIComponent(value)}`];
162
+
163
+ parts.push(`Path=${options.path ?? "/"}`);
164
+ if (options.domain) parts.push(`Domain=${options.domain}`);
165
+ if (options.maxAge !== undefined) parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
166
+ if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
167
+ if (options.httpOnly !== false) parts.push("HttpOnly");
168
+ if (options.secure ?? process.env.NODE_ENV !== "development") parts.push("Secure");
169
+ parts.push(`SameSite=${options.sameSite ?? "Lax"}`);
170
+
171
+ return parts.join("; ");
172
+ }
173
+
174
+ /**
175
+ * @param {import('http').ServerResponse} res
176
+ * @param {string} name
177
+ * @param {string} value
178
+ * @param {CookieOptions} [options]
179
+ */
180
+ export function setCookie(res, name, value, options = {}) {
181
+ const existing = res.getHeader("Set-Cookie");
182
+ const serialized = serializeCookie(name, value, options);
183
+
184
+ /** @type {string[]} */
185
+ const all = existing
186
+ ? Array.isArray(existing)
187
+ ? [...existing.map(String)]
188
+ : [String(existing)]
189
+ : [];
190
+
191
+ all.push(serialized);
192
+ res.setHeader("Set-Cookie", all);
193
+ }
194
+
195
+ /**
196
+ * @param {import('http').ServerResponse} res
197
+ * @param {string} name
198
+ * @param {CookieOptions} [options]
199
+ */
200
+ export function clearCookie(res, name, options = {}) {
201
+ setCookie(res, name, "", { ...options, maxAge: 0, expires: new Date(0) });
202
+ }
203
+
204
+ /**
205
+ * İmzalı cookie yazar. Değer okunabilir kalır (şifreleme değil, imza);
206
+ * gizli kalması gereken veriyi cookie'ye koymayın, kimliğini koyun.
207
+ *
208
+ * @param {import('http').ServerResponse} res
209
+ * @param {string} name
210
+ * @param {string} value
211
+ * @param {CookieOptions} [options]
212
+ */
213
+ export function setSignedCookie(res, name, value, options = {}) {
214
+ const encoded = base64url(value);
215
+ setCookie(res, name, `${encoded}.${sign(encoded)}`, options);
216
+ }
217
+
218
+ /**
219
+ * İmzalı cookie okur. İmza uymuyorsa `null` — bozuk imza, yok sayılmalı,
220
+ * "belki geçerlidir" diye kullanılmamalı.
221
+ *
222
+ * @param {import('http').IncomingMessage} req
223
+ * @param {string} name
224
+ * @returns {string | null}
225
+ */
226
+ export function getSignedCookie(req, name) {
227
+ const raw = parseCookies(req)[name];
228
+ if (!raw) return null;
229
+
230
+ const index = raw.lastIndexOf(".");
231
+ if (index <= 0) return null;
232
+
233
+ const encoded = raw.slice(0, index);
234
+ const signature = raw.slice(index + 1);
235
+
236
+ let expected;
237
+ try {
238
+ expected = sign(encoded);
239
+ } catch {
240
+ // Sır yoksa imzalı okuma sessizce başarısız olur: sunucu ayakta kalır
241
+ // ama hiçbir oturum geçerli sayılmaz.
242
+ return null;
243
+ }
244
+
245
+ if (!safeEqual(signature, expected)) return null;
246
+ return fromBase64url(encoded);
247
+ }
248
+
249
+ /**
250
+ * Kriptografik rastgele token. CSRF token'ı ve oturum kimliği için.
251
+ *
252
+ * @param {number} [bytes]
253
+ * @returns {string}
254
+ */
255
+ export function randomToken(bytes = 32) {
256
+ return crypto.randomBytes(bytes).toString("base64url");
257
+ }
@@ -1,46 +1,46 @@
1
- /**
2
- * React `cache()` fonksiyonunun karşılığı: aynı istek içinde aynı argümanlarla
3
- * yapılan çağrılar tek kez çalışır. İstek bağlamı yoksa (script, client)
4
- * memoizasyon devre dışı kalır ve fonksiyon doğrudan çağrılır.
5
- */
6
- import { AsyncLocalStorage } from "node:async_hooks";
7
-
8
- /** @type {AsyncLocalStorage<Map<unknown, Map<string, unknown>>>} */
9
- const storage = new AsyncLocalStorage();
10
-
11
- /**
12
- * İsteği bir memo bağlamı içinde çalıştırır.
13
- * @param {() => T} run
14
- * @returns {T}
15
- * @template T
16
- */
17
- export function withRequestCache(run) {
18
- return storage.run(new Map(), run);
19
- }
20
-
21
- /**
22
- * @param {F} fn
23
- * @returns {F}
24
- * @template {(...args: any[]) => any} F
25
- */
26
- export function cache(fn) {
27
- const wrapped = (...args) => {
28
- const contextStore = storage.getStore();
29
- if (!contextStore) return fn(...args);
30
-
31
- let perFn = contextStore.get(fn);
32
- if (!perFn) {
33
- perFn = new Map();
34
- contextStore.set(fn, perFn);
35
- }
36
-
37
- const key = args.length === 0 ? "" : JSON.stringify(args);
38
- if (perFn.has(key)) return perFn.get(key);
39
-
40
- const result = fn(...args);
41
- perFn.set(key, result);
42
- return result;
43
- };
44
-
45
- return /** @type {F} */ (wrapped);
46
- }
1
+ /**
2
+ * React `cache()` fonksiyonunun karşılığı: aynı istek içinde aynı argümanlarla
3
+ * yapılan çağrılar tek kez çalışır. İstek bağlamı yoksa (script, client)
4
+ * memoizasyon devre dışı kalır ve fonksiyon doğrudan çağrılır.
5
+ */
6
+ import { AsyncLocalStorage } from "node:async_hooks";
7
+
8
+ /** @type {AsyncLocalStorage<Map<unknown, Map<string, unknown>>>} */
9
+ const storage = new AsyncLocalStorage();
10
+
11
+ /**
12
+ * İsteği bir memo bağlamı içinde çalıştırır.
13
+ * @param {() => T} run
14
+ * @returns {T}
15
+ * @template T
16
+ */
17
+ export function withRequestCache(run) {
18
+ return storage.run(new Map(), run);
19
+ }
20
+
21
+ /**
22
+ * @param {F} fn
23
+ * @returns {F}
24
+ * @template {(...args: any[]) => any} F
25
+ */
26
+ export function cache(fn) {
27
+ const wrapped = (...args) => {
28
+ const contextStore = storage.getStore();
29
+ if (!contextStore) return fn(...args);
30
+
31
+ let perFn = contextStore.get(fn);
32
+ if (!perFn) {
33
+ perFn = new Map();
34
+ contextStore.set(fn, perFn);
35
+ }
36
+
37
+ const key = args.length === 0 ? "" : JSON.stringify(args);
38
+ if (perFn.has(key)) return perFn.get(key);
39
+
40
+ const result = fn(...args);
41
+ perFn.set(key, result);
42
+ return result;
43
+ };
44
+
45
+ return /** @type {F} */ (wrapped);
46
+ }