jskelet 0.2.5 → 0.3.0

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 (106) hide show
  1. package/AGENTS.md +132 -132
  2. package/CHANGELOG.md +403 -383
  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 +293 -287
  7. package/docs/03-routing.md +486 -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 +1231 -1209
  11. package/docs/07-yapilandirma.md +44 -21
  12. package/docs/08-build.md +366 -366
  13. package/docs/09-dev-araclari.md +335 -335
  14. package/docs/10-dagitim.md +329 -329
  15. package/docs/11-tasima.md +1 -0
  16. package/docs/12-panel-ve-oturum.md +384 -384
  17. package/docs/README.md +105 -105
  18. package/docs/en/01-getting-started.md +292 -292
  19. package/docs/en/02-architecture.md +311 -305
  20. package/docs/en/03-routing.md +503 -497
  21. package/docs/en/04-rendering.md +504 -504
  22. package/docs/en/05-islands.md +492 -492
  23. package/docs/en/06-caching.md +1197 -1198
  24. package/docs/en/07-configuration.md +1009 -986
  25. package/docs/en/08-build.md +383 -383
  26. package/docs/en/09-dev-tools.md +342 -342
  27. package/docs/en/10-deployment.md +332 -332
  28. package/docs/en/11-migration.md +360 -359
  29. package/docs/en/12-dashboards-and-sessions.md +392 -392
  30. package/docs/en/README.md +112 -112
  31. package/package.json +102 -102
  32. package/src/build/ensure-build.mjs +15 -15
  33. package/src/build/paths.mjs +143 -143
  34. package/src/build/resolve-peer.mjs +36 -36
  35. package/src/build/tasks/client.mjs +268 -268
  36. package/src/build/tasks/css.mjs +124 -124
  37. package/src/build/tasks/fonts.mjs +146 -146
  38. package/src/build/tasks/icons.mjs +224 -224
  39. package/src/build/tasks/images.mjs +244 -244
  40. package/src/build/tasks/precompress.mjs +78 -78
  41. package/src/client/{cache-panel → admin}/i18n.js +756 -670
  42. package/src/client/{cache-panel → admin}/login.html +74 -74
  43. package/src/client/{cache-panel → admin}/panel.css +804 -756
  44. package/src/client/admin/panel.html +486 -0
  45. package/src/client/{cache-panel → admin}/panel.js +1242 -915
  46. package/src/client/devtools/report.html +185 -185
  47. package/src/client/devtools/report.js +725 -725
  48. package/src/client/dom.js +95 -95
  49. package/src/client/form.js +192 -192
  50. package/src/client/index.js +35 -35
  51. package/src/client/registry.js +297 -297
  52. package/src/client/safe-image.js +91 -91
  53. package/src/client/store.js +36 -36
  54. package/src/client/swap.js +188 -188
  55. package/src/config/defaults.js +16 -7
  56. package/src/config/index.js +38 -22
  57. package/src/config/pattern.js +107 -107
  58. package/src/http/control-flow.js +71 -71
  59. package/src/http/cookies.js +257 -257
  60. package/src/http/request-cache.js +46 -46
  61. package/src/http/request-context.js +162 -162
  62. package/src/index.js +83 -83
  63. package/src/init.mjs +221 -221
  64. package/src/log.mjs +58 -0
  65. package/src/runtime/alias-hooks.mjs +119 -119
  66. package/src/runtime/register.mjs +4 -4
  67. package/src/server/admin/actions.js +229 -0
  68. package/src/server/admin/auth.js +125 -0
  69. package/src/server/admin/event-log.js +151 -0
  70. package/src/server/admin/gate.js +209 -0
  71. package/src/server/admin/inventory.js +188 -0
  72. package/src/server/admin/mount.js +56 -0
  73. package/src/server/admin/router.js +216 -0
  74. package/src/server/admin/snapshot.js +126 -0
  75. package/src/server/assets.js +147 -147
  76. package/src/server/cache-deps.js +42 -42
  77. package/src/server/cloudflare.js +607 -607
  78. package/src/server/create-app.js +295 -291
  79. package/src/server/data-cache.js +462 -462
  80. package/src/server/dev/report.js +369 -369
  81. package/src/server/dev/socket.js +170 -170
  82. package/src/server/dev/version-check.mjs +139 -139
  83. package/src/server/html-cache.js +817 -817
  84. package/src/server/metadata.js +102 -102
  85. package/src/server/middleware/compression.js +205 -205
  86. package/src/server/middleware/csrf.js +134 -134
  87. package/src/server/middleware/dev-gate.js +62 -62
  88. package/src/server/middleware/headers.js +37 -37
  89. package/src/server/middleware/redirects.js +32 -32
  90. package/src/server/middleware/static-precompressed.js +100 -100
  91. package/src/server/middleware/trailing-slash.js +53 -0
  92. package/src/server/middleware/upstream-proxy.js +141 -141
  93. package/src/server/prewarm.js +601 -601
  94. package/src/server/redis.js +569 -569
  95. package/src/server/router.js +128 -128
  96. package/src/server/status-page.js +164 -164
  97. package/src/server/upstream-limiter.js +376 -376
  98. package/src/server/upstream-tracking.js +166 -166
  99. package/src/start.mjs +7 -7
  100. package/src/templates/layout.ejs +44 -44
  101. package/src/version.mjs +31 -31
  102. package/src/views/components/loader.js +85 -85
  103. package/src/views/helpers/html.js +102 -102
  104. package/src/views/helpers/tags.js +245 -245
  105. package/src/client/cache-panel/panel.html +0 -308
  106. package/src/server/cache-panel.js +0 -759
@@ -17,8 +17,8 @@
17
17
  * rewrites() → [{ source, destination }] | { beforeFiles?, afterFiles? }
18
18
  * cache() → { html?: { [source]: saniye },
19
19
  * query?: { [source]: string[] | true }, maxEntries?: number,
20
- * data?: {...}, redis?: {...}, prewarm?: {...},
21
- * panel?: {...} }
20
+ * data?: {...}, redis?: {...}, prewarm?: {...} }
21
+ * admin() → { enabled?, basePath?, allowIps?, blockBots?, … }
22
22
  *
23
23
  * Fonksiyon olmayan bölümler (`brand`, `security`, `static`, `navigation`…)
24
24
  * düz nesne olarak okunur.
@@ -29,8 +29,8 @@ import process from "node:process";
29
29
  import { pathToFileURL } from "node:url";
30
30
  import { compilePattern, matchPattern } from "./pattern.js";
31
31
  import {
32
+ DEFAULT_ADMIN,
32
33
  DEFAULT_BRAND,
33
- DEFAULT_CACHE_PANEL,
34
34
  DEFAULT_CLOUDFLARE,
35
35
  DEFAULT_DATA_CACHE,
36
36
  DEFAULT_DEV_GATE_BYPASS,
@@ -96,7 +96,7 @@ const CONFIG_FILE = "jskelet.config.mjs";
96
96
  * @property {{ attempts: number, delayMs: number }} transientRetry
97
97
  * @property {RedisConfig} redis Opsiyonel Redis ikinci kademesi.
98
98
  * @property {typeof DEFAULT_UPSTREAM_LIMIT} upstream Upstream hız freni.
99
- * @property {typeof DEFAULT_CACHE_PANEL} cachePanel Önbellek yönetim paneli.
99
+ * @property {typeof DEFAULT_ADMIN} admin Framework yönetim paneli.
100
100
  * @property {typeof DEFAULT_CLOUDFLARE} cloudflare Cloudflare cache yüzeyi.
101
101
  * @property {Record<string, unknown>} prewarm
102
102
  * @property {{ source: string, test: (pathname: string) => boolean }[]} prewarmPriority
@@ -104,6 +104,7 @@ const CONFIG_FILE = "jskelet.config.mjs";
104
104
  * @property {Record<string, Function>} hooks
105
105
  * @property {string} layout Layout `.ejs` dosyasının mutlak yolu.
106
106
  * @property {string[] | null} routes Açık route modülü listesi.
107
+ * @property {boolean} trailingSlash URL'ler `/` ile bitsin mi (Next `trailingSlash`).
107
108
  * @property {{ extensions: Set<string>, prefixes: string[] }} static
108
109
  * @property {string[]} devGateBypass
109
110
  * @property {string[]} preconnect
@@ -301,46 +302,58 @@ function normalizeUpstream(raw) {
301
302
  }
302
303
 
303
304
  /**
304
- * Önbellek panelinin bölümü.
305
- *
306
- * `enabled` yalnızca açıkça `true` verildiğinde ya da `JSKELET_CACHE_PANEL`
307
- * ortam değişkeni ayarlandığında açılır: paneli yanlışlıkla açmanın bedeli,
308
- * önbelleği boşaltabilen bir ucu internete koymak.
305
+ * Yönetim paneli. `enabled` yalnızca açıkça `true` verildiğinde ya da
306
+ * `JSKELET_ADMIN` ortam değişkeni ayarlandığında açılır: paneli yanlışlıkla
307
+ * açmanın bedeli, önbelleği boşaltabilen bir ucu internete koymak.
309
308
  *
310
309
  * Ortam değişkeni config'in **üstünde** duruyor, çünkü paneli genelde bir
311
310
  * arıza sırasında tek seferlik açmak isteniyor ve o an config dosyasını
312
- * değiştirip yeniden dağıtmak istenmiyor. `JSKELET_CACHE_PANEL=0` aynı
313
- * mantıkla config'te açık olan paneli kapatır.
311
+ * değiştirip yeniden dağıtmak istenmiyor. `JSKELET_ADMIN=0` aynı mantıkla
312
+ * config'te açık olan paneli kapatır.
314
313
  *
315
314
  * @param {unknown} raw
316
- * @returns {typeof DEFAULT_CACHE_PANEL}
315
+ * @returns {typeof DEFAULT_ADMIN}
317
316
  */
318
- function normalizeCachePanel(raw) {
317
+ function normalizeAdmin(raw) {
319
318
  const source = /** @type {Record<string, any>} */ (raw ?? {});
320
- const env = process.env.JSKELET_CACHE_PANEL;
319
+ const env = process.env.JSKELET_ADMIN;
321
320
 
322
321
  const basePath =
323
322
  typeof source.basePath === "string" && source.basePath.startsWith("/")
324
323
  ? source.basePath.replace(/\/+$/, "")
325
- : DEFAULT_CACHE_PANEL.basePath;
324
+ : DEFAULT_ADMIN.basePath;
326
325
 
327
326
  /** @param {string} key @param {number} min */
328
327
  const positive = (key, min) => {
329
328
  const value = Number(source[key]);
330
329
  return Number.isFinite(value) && value >= min
331
330
  ? value
332
- : /** @type {any} */ (DEFAULT_CACHE_PANEL)[key];
331
+ : /** @type {any} */ (DEFAULT_ADMIN)[key];
333
332
  };
334
333
 
334
+ const allowIps = Array.isArray(source.allowIps)
335
+ ? source.allowIps
336
+ .filter((entry) => typeof entry === "string" && entry.trim())
337
+ .map((entry) => entry.trim())
338
+ : [...DEFAULT_ADMIN.allowIps];
339
+
340
+ const logSize = Number(source.logSize);
341
+
335
342
  return {
336
343
  enabled:
337
344
  env === undefined
338
345
  ? source.enabled === true
339
346
  : env !== "0" && env !== "false" && env !== "",
340
- basePath: basePath || DEFAULT_CACHE_PANEL.basePath,
347
+ basePath: basePath || DEFAULT_ADMIN.basePath,
348
+ allowIps,
349
+ blockBots: source.blockBots !== false,
341
350
  banAttempts: Math.floor(positive("banAttempts", 1)),
342
351
  banHours: positive("banHours", 0),
343
352
  sessionHours: positive("sessionHours", 0),
353
+ logSize:
354
+ Number.isFinite(logSize) && logSize >= 50
355
+ ? Math.min(5000, Math.floor(logSize))
356
+ : DEFAULT_ADMIN.logSize,
344
357
  };
345
358
  }
346
359
 
@@ -423,7 +436,6 @@ function normalizeQueryRules(raw) {
423
436
  * transientRetry: { attempts: number, delayMs: number },
424
437
  * redis: RedisConfig,
425
438
  * upstream: typeof DEFAULT_UPSTREAM_LIMIT,
426
- * cachePanel: typeof DEFAULT_CACHE_PANEL,
427
439
  * cloudflare: typeof DEFAULT_CLOUDFLARE,
428
440
  * prewarm: Record<string, unknown>,
429
441
  * prewarmPriority: ResolvedConfig["prewarmPriority"] }}
@@ -464,7 +476,6 @@ function normalizeCache(raw) {
464
476
  : { ...DEFAULT_TRANSIENT_RETRY, ...(raw?.transientRetry ?? {}) },
465
477
  redis: normalizeRedis(raw?.redis),
466
478
  upstream: normalizeUpstream(raw?.upstream),
467
- cachePanel: normalizeCachePanel(raw?.panel),
468
479
  cloudflare: normalizeCloudflare(raw?.cloudflare),
469
480
  // Desenler derlenmiş hâlde ayrı alanda tutulur: `prewarm` sayısal
470
481
  // ayarların düz torbası olarak kalsın, her turda yeniden derlenmesin.
@@ -669,11 +680,12 @@ export async function loadConfig(options = {}) {
669
680
  }
670
681
  };
671
682
 
672
- const [headers, redirects, rewrites, cache] = await Promise.all([
683
+ const [headers, redirects, rewrites, cache, admin] = await Promise.all([
673
684
  section("headers"),
674
685
  section("redirects"),
675
686
  section("rewrites"),
676
687
  section("cache"),
688
+ section("admin"),
677
689
  ]);
678
690
 
679
691
  const {
@@ -686,7 +698,6 @@ export async function loadConfig(options = {}) {
686
698
  transientRetry,
687
699
  redis,
688
700
  upstream,
689
- cachePanel,
690
701
  cloudflare,
691
702
  prewarm,
692
703
  prewarmPriority,
@@ -710,7 +721,7 @@ export async function loadConfig(options = {}) {
710
721
  transientRetry,
711
722
  redis,
712
723
  upstream,
713
- cachePanel,
724
+ admin: normalizeAdmin(admin),
714
725
  cloudflare,
715
726
  prewarm,
716
727
  prewarmPriority,
@@ -718,6 +729,11 @@ export async function loadConfig(options = {}) {
718
729
  hooks: source.hooks ?? {},
719
730
  layout: resolveLayout(dirs, source.layout),
720
731
  routes: Array.isArray(source.routes) ? source.routes : null,
732
+ // Varsayılan kapalı: açıkken `/hakkinda` → 308 `/hakkinda/` ve kanonik
733
+ // yanıt 200'dir. Kapalıyken slash dayatılmaz — Express'in non-strict
734
+ // eşleşmesi her iki biçimi de 200 ile servis eder (Next'in varsayılan
735
+ // "slash'ı kırp" davranışından bilinçli fark).
736
+ trailingSlash: source.trailingSlash === true,
721
737
  static: {
722
738
  extensions: new Set(source.static?.extensions ?? DEFAULT_STATIC.extensions),
723
739
  prefixes: source.static?.prefixes ?? DEFAULT_STATIC.prefixes,
@@ -1,107 +1,107 @@
1
- /**
2
- * `next.config` `source`/`destination` sözdiziminin küçük bir derleyicisi.
3
- *
4
- * Desteklenen biçimler:
5
- * `/haber/:slug` → tek segment yakalar
6
- * `/:path*` → sıfır veya daha fazla segment yakalar
7
- * `/:path*.svg` → joker + sabit son ek (uzantı kuralları böyle yazılır)
8
- * `/etiket-:slug` → segment ortasında parametre
9
- *
10
- * Yakalanan değerler `destination` içindeki aynı adlı `:param`'lara yazılır.
11
- * Next'in tam `path-to-regexp` yüzeyi değil; config'te fiilen kullanılan alt
12
- * küme bilinçli olarak seçildi ve tanınmayan bir sözdizimi sessizce literal
13
- * kabul edilmez, uyarı üretir.
14
- */
15
-
16
- /** @typedef {{ regex: RegExp, keys: string[], source: string }} CompiledPattern */
17
-
18
- const PARAM = /^:([A-Za-z_][A-Za-z0-9_]*)(\*)?/;
19
-
20
- /** @param {string} text */
21
- function escapeLiteral(text) {
22
- return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23
- }
24
-
25
- /**
26
- * @param {string} source
27
- * @returns {CompiledPattern | null}
28
- */
29
- export function compilePattern(source) {
30
- if (typeof source !== "string" || !source.startsWith("/")) {
31
- console.warn(`[config] invalid source (must start with \`/\`): ${source}`);
32
- return null;
33
- }
34
-
35
- /** @type {string[]} */
36
- const keys = [];
37
- let pattern = "";
38
- let index = 0;
39
-
40
- while (index < source.length) {
41
- const char = source[index];
42
-
43
- if (char !== ":") {
44
- pattern += escapeLiteral(char);
45
- index += 1;
46
- continue;
47
- }
48
-
49
- const match = source.slice(index).match(PARAM);
50
- if (!match) {
51
- pattern += escapeLiteral(char);
52
- index += 1;
53
- continue;
54
- }
55
-
56
- const [raw, name, wildcard] = match;
57
- keys.push(name);
58
-
59
- if (!wildcard) {
60
- // Tek segmentli parametre segment sınırını aşmaz.
61
- pattern += "([^/]+)";
62
- index += raw.length;
63
- continue;
64
- }
65
-
66
- // Joker parametre segment sınırlarını aşar ve **sıfır** segment de
67
- // yakalar: `/hesabim/:path*` kuralı `/hesabim`i de kapsamalı, aksi hâlde
68
- // bir bölümün tamamını kapatmak isteyen kural kök yolu atlıyor. Bunun için
69
- // hemen öncesindeki `/` de opsiyonele alınır.
70
- const trailingSlash = pattern.endsWith("/");
71
- pattern = trailingSlash ? `${pattern.slice(0, -1)}(?:/(.*))?` : `${pattern}(.*)`;
72
- index += raw.length;
73
- }
74
-
75
- return { regex: new RegExp(`^${pattern}$`), keys, source };
76
- }
77
-
78
- /**
79
- * @param {CompiledPattern} compiled
80
- * @param {string} pathname
81
- * @returns {Record<string, string> | null}
82
- */
83
- export function matchPattern(compiled, pathname) {
84
- const match = compiled.regex.exec(pathname);
85
- if (!match) return null;
86
-
87
- /** @type {Record<string, string>} */
88
- const params = {};
89
- compiled.keys.forEach((key, i) => {
90
- params[key] = match[i + 1] ?? "";
91
- });
92
- return params;
93
- }
94
-
95
- /**
96
- * `destination` içindeki `:param` yer tutucularını doldurur.
97
- *
98
- * @param {string} destination
99
- * @param {Record<string, string>} params
100
- * @returns {string}
101
- */
102
- export function fillDestination(destination, params) {
103
- return destination.replace(
104
- /:([A-Za-z_][A-Za-z0-9_]*)(\*)?/g,
105
- (raw, name) => params[name] ?? raw,
106
- );
107
- }
1
+ /**
2
+ * `next.config` `source`/`destination` sözdiziminin küçük bir derleyicisi.
3
+ *
4
+ * Desteklenen biçimler:
5
+ * `/haber/:slug` → tek segment yakalar
6
+ * `/:path*` → sıfır veya daha fazla segment yakalar
7
+ * `/:path*.svg` → joker + sabit son ek (uzantı kuralları böyle yazılır)
8
+ * `/etiket-:slug` → segment ortasında parametre
9
+ *
10
+ * Yakalanan değerler `destination` içindeki aynı adlı `:param`'lara yazılır.
11
+ * Next'in tam `path-to-regexp` yüzeyi değil; config'te fiilen kullanılan alt
12
+ * küme bilinçli olarak seçildi ve tanınmayan bir sözdizimi sessizce literal
13
+ * kabul edilmez, uyarı üretir.
14
+ */
15
+
16
+ /** @typedef {{ regex: RegExp, keys: string[], source: string }} CompiledPattern */
17
+
18
+ const PARAM = /^:([A-Za-z_][A-Za-z0-9_]*)(\*)?/;
19
+
20
+ /** @param {string} text */
21
+ function escapeLiteral(text) {
22
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23
+ }
24
+
25
+ /**
26
+ * @param {string} source
27
+ * @returns {CompiledPattern | null}
28
+ */
29
+ export function compilePattern(source) {
30
+ if (typeof source !== "string" || !source.startsWith("/")) {
31
+ console.warn(`[config] invalid source (must start with \`/\`): ${source}`);
32
+ return null;
33
+ }
34
+
35
+ /** @type {string[]} */
36
+ const keys = [];
37
+ let pattern = "";
38
+ let index = 0;
39
+
40
+ while (index < source.length) {
41
+ const char = source[index];
42
+
43
+ if (char !== ":") {
44
+ pattern += escapeLiteral(char);
45
+ index += 1;
46
+ continue;
47
+ }
48
+
49
+ const match = source.slice(index).match(PARAM);
50
+ if (!match) {
51
+ pattern += escapeLiteral(char);
52
+ index += 1;
53
+ continue;
54
+ }
55
+
56
+ const [raw, name, wildcard] = match;
57
+ keys.push(name);
58
+
59
+ if (!wildcard) {
60
+ // Tek segmentli parametre segment sınırını aşmaz.
61
+ pattern += "([^/]+)";
62
+ index += raw.length;
63
+ continue;
64
+ }
65
+
66
+ // Joker parametre segment sınırlarını aşar ve **sıfır** segment de
67
+ // yakalar: `/hesabim/:path*` kuralı `/hesabim`i de kapsamalı, aksi hâlde
68
+ // bir bölümün tamamını kapatmak isteyen kural kök yolu atlıyor. Bunun için
69
+ // hemen öncesindeki `/` de opsiyonele alınır.
70
+ const trailingSlash = pattern.endsWith("/");
71
+ pattern = trailingSlash ? `${pattern.slice(0, -1)}(?:/(.*))?` : `${pattern}(.*)`;
72
+ index += raw.length;
73
+ }
74
+
75
+ return { regex: new RegExp(`^${pattern}$`), keys, source };
76
+ }
77
+
78
+ /**
79
+ * @param {CompiledPattern} compiled
80
+ * @param {string} pathname
81
+ * @returns {Record<string, string> | null}
82
+ */
83
+ export function matchPattern(compiled, pathname) {
84
+ const match = compiled.regex.exec(pathname);
85
+ if (!match) return null;
86
+
87
+ /** @type {Record<string, string>} */
88
+ const params = {};
89
+ compiled.keys.forEach((key, i) => {
90
+ params[key] = match[i + 1] ?? "";
91
+ });
92
+ return params;
93
+ }
94
+
95
+ /**
96
+ * `destination` içindeki `:param` yer tutucularını doldurur.
97
+ *
98
+ * @param {string} destination
99
+ * @param {Record<string, string>} params
100
+ * @returns {string}
101
+ */
102
+ export function fillDestination(destination, params) {
103
+ return destination.replace(
104
+ /:([A-Za-z_][A-Za-z0-9_]*)(\*)?/g,
105
+ (raw, name) => params[name] ?? raw,
106
+ );
107
+ }
@@ -1,71 +1,71 @@
1
- /**
2
- * `next/navigation` içindeki notFound/redirect kontrol akışının karşılığı.
3
- * Derinlerdeki bir fonksiyon throw eder, Express error handler yakalar.
4
- */
5
-
6
- export class NotFoundError extends Error {
7
- constructor(message = "Not Found") {
8
- super(message);
9
- this.name = "NotFoundError";
10
- this.statusCode = 404;
11
- }
12
- }
13
-
14
- export class RedirectError extends Error {
15
- /**
16
- * @param {string} location
17
- * @param {301 | 302 | 303 | 307 | 308} [statusCode]
18
- */
19
- constructor(location, statusCode = 307) {
20
- super(`Redirect to ${location}`);
21
- this.name = "RedirectError";
22
- this.location = location;
23
- this.statusCode = statusCode;
24
- }
25
- }
26
-
27
- /** @returns {never} */
28
- export function notFound() {
29
- throw new NotFoundError();
30
- }
31
-
32
- /**
33
- * @param {string} location
34
- * @returns {never}
35
- */
36
- export function permanentRedirect(location) {
37
- throw new RedirectError(location, 308);
38
- }
39
-
40
- /**
41
- * @param {string} location
42
- * @returns {never}
43
- */
44
- export function redirect(location) {
45
- throw new RedirectError(location, 307);
46
- }
47
-
48
- /**
49
- * POST sonrası yönlendirme (303 See Other).
50
- *
51
- * `redirect()` 307 kullanır ve 307 **metodu korur**: bir POST handler'ından
52
- * çağrıldığında tarayıcı hedefe yeniden POST eder. Form gönderiminden sonra
53
- * sayfayı GET olarak açmak — yani geri tuşunun formu yeniden göndermediği
54
- * klasik "post/redirect/get" akışı — 303 gerektiriyor.
55
- *
56
- * @param {string} location
57
- * @returns {never}
58
- */
59
- export function seeOther(location) {
60
- throw new RedirectError(location, 303);
61
- }
62
-
63
- /** @param {unknown} error */
64
- export function isNotFoundError(error) {
65
- return error instanceof NotFoundError;
66
- }
67
-
68
- /** @param {unknown} error */
69
- export function isRedirectError(error) {
70
- return error instanceof RedirectError;
71
- }
1
+ /**
2
+ * `next/navigation` içindeki notFound/redirect kontrol akışının karşılığı.
3
+ * Derinlerdeki bir fonksiyon throw eder, Express error handler yakalar.
4
+ */
5
+
6
+ export class NotFoundError extends Error {
7
+ constructor(message = "Not Found") {
8
+ super(message);
9
+ this.name = "NotFoundError";
10
+ this.statusCode = 404;
11
+ }
12
+ }
13
+
14
+ export class RedirectError extends Error {
15
+ /**
16
+ * @param {string} location
17
+ * @param {301 | 302 | 303 | 307 | 308} [statusCode]
18
+ */
19
+ constructor(location, statusCode = 307) {
20
+ super(`Redirect to ${location}`);
21
+ this.name = "RedirectError";
22
+ this.location = location;
23
+ this.statusCode = statusCode;
24
+ }
25
+ }
26
+
27
+ /** @returns {never} */
28
+ export function notFound() {
29
+ throw new NotFoundError();
30
+ }
31
+
32
+ /**
33
+ * @param {string} location
34
+ * @returns {never}
35
+ */
36
+ export function permanentRedirect(location) {
37
+ throw new RedirectError(location, 308);
38
+ }
39
+
40
+ /**
41
+ * @param {string} location
42
+ * @returns {never}
43
+ */
44
+ export function redirect(location) {
45
+ throw new RedirectError(location, 307);
46
+ }
47
+
48
+ /**
49
+ * POST sonrası yönlendirme (303 See Other).
50
+ *
51
+ * `redirect()` 307 kullanır ve 307 **metodu korur**: bir POST handler'ından
52
+ * çağrıldığında tarayıcı hedefe yeniden POST eder. Form gönderiminden sonra
53
+ * sayfayı GET olarak açmak — yani geri tuşunun formu yeniden göndermediği
54
+ * klasik "post/redirect/get" akışı — 303 gerektiriyor.
55
+ *
56
+ * @param {string} location
57
+ * @returns {never}
58
+ */
59
+ export function seeOther(location) {
60
+ throw new RedirectError(location, 303);
61
+ }
62
+
63
+ /** @param {unknown} error */
64
+ export function isNotFoundError(error) {
65
+ return error instanceof NotFoundError;
66
+ }
67
+
68
+ /** @param {unknown} error */
69
+ export function isRedirectError(error) {
70
+ return error instanceof RedirectError;
71
+ }