jskelet 0.2.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -87,12 +87,14 @@ one is listed under a **Breaking** heading.
87
87
 
88
88
  ### Changed
89
89
 
90
- - Request errors raised during a prewarm pass are no longer logged one by one.
91
- They are counted while the pass runs and printed as a single summary line
92
- afterwards, grouped by status and message with the most frequent kinds first,
93
- so a flaky upstream can no longer bury the "warmed N/M pages" line under
94
- hundreds of stack traces. Errors from real traffic are logged as before, and
95
- the dev tools panel still shows the per-path detail.
90
+ - Errors and warnings raised during a prewarm pass are no longer logged one per
91
+ page. Request errors and the per-page render warnings (`was produced with
92
+ missing data`, `returned notFound() while upstream is failing`, `could not be
93
+ produced`) are counted while the pass runs and printed as a single summary
94
+ block afterwards, grouped by message with the most frequent kinds first, so a
95
+ failing upstream can no longer bury the "warmed N/M pages" line under hundreds
96
+ of near-identical lines. Real traffic logs as before, and the dev tools panel
97
+ still shows the per-path detail.
96
98
  - The marketing example's changelog page is now a timeline: releases are laid out
97
99
  along a rail with a sticky version column, each change group gets its own card
98
100
  with a coloured rule and item count, and a row of version chips at the top
package/docs/06-cache.md CHANGED
@@ -706,13 +706,15 @@ Kurallar:
706
706
  5. Özet loglanır:
707
707
  `[prewarm] warmed 128/130 pages, 2 failed, 5 recovered on the retry pass (12.4s)`
708
708
 
709
- Tur sırasında oluşan istek hataları tek tek loglanmaz; sayılır ve tur bitince
710
- özetin ardından tek bir satırda, en sık görülen türler başta olacak şekilde
711
- basılır:
709
+ Tur sırasında oluşan istek hataları ve sayfa başına basılan render uyarıları
710
+ (`was produced with missing data`, `returned notFound() while upstream is
711
+ failing`, `could not be produced`) tek tek loglanmaz; sayılır ve tur bitince
712
+ özetin ardından, en sık görülen türler başta olacak şekilde basılır:
712
713
 
713
714
  ```text
714
- [prewarm] 37 request errors were not logged individually:
715
- 31× 502 upstream fetch failed: /api/quotes
715
+ [prewarm] 137 problems were not logged individually:
716
+ 94× missing data, upstream is failing permanently (403 /api/v1/polls)
717
+ 37× missing data, upstream is failing permanently (400 /api/v1/posts)
716
718
  6× 500 Cannot read properties of undefined (reading 'title')
717
719
  ```
718
720
 
@@ -727,13 +727,15 @@ Rules:
727
727
  5. A summary is logged:
728
728
  `[prewarm] warmed 128/130 pages, 2 failed, 5 recovered on the retry pass (12.4s)`
729
729
 
730
- Request errors raised during the pass are not logged one by one. They are
731
- counted while the pass runs and printed after the summary as a single line,
732
- grouped by status and message with the most frequent kinds first:
730
+ Request errors and the per-page render warnings (`was produced with missing
731
+ data`, `returned notFound() while upstream is failing`, `could not be
732
+ produced`) raised during the pass are not logged one by one. They are counted
733
+ while the pass runs and printed after the summary, most frequent kinds first:
733
734
 
734
735
  ```text
735
- [prewarm] 37 request errors were not logged individually:
736
- 31× 502 upstream fetch failed: /api/quotes
736
+ [prewarm] 137 problems were not logged individually:
737
+ 94× missing data, upstream is failing permanently (403 /api/v1/polls)
738
+ 37× missing data, upstream is failing permanently (400 /api/v1/posts)
737
739
  6× 500 Cannot read properties of undefined (reading 'title')
738
740
  ```
739
741
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jskelet",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A framework that feels like no framework: Express 5 + EJS server rendering, vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,6 +20,7 @@
20
20
  */
21
21
  import process from "node:process";
22
22
  import { getConfig, hook } from "../config/index.js";
23
+ import { getRequestContext } from "../http/request-context.js";
23
24
  import { takeInvalidatedPaths } from "./html-cache.js";
24
25
 
25
26
  /**
@@ -45,11 +46,13 @@ export const prewarmProgress = {
45
46
  };
46
47
 
47
48
  /**
48
- * Isıtma turu sırasında bastırılan istek hataları: mesaj → kaç kez görüldü.
49
+ * Isıtma turu sırasında bastırılan uyarılar: mesaj → kaç kez görüldü.
49
50
  *
50
51
  * Yüzlerce yolu tarayan bir tur, upstream bir an için tıksırdığında yüzlerce
51
- * yığın izini loga döküyor ve asıl bilgi (kaç sayfa ısındı) kayboluyor. Tur
52
- * boyunca hatalar burada toplanır, tur bitince tek satırda özetlenir.
52
+ * satır loga döküyor ve asıl bilgi (kaç sayfa ısındı) kayboluyor. Tur boyunca
53
+ * mesajlar burada toplanır, tur bitince tek satırda özetlenir. Yol adı
54
+ * anahtara girmez: gruplanabilmesi için mesajın kendisi yeterli, tek bir yolun
55
+ * ayrıntısı zaten `entries` üzerinden dev panelinde görünüyor.
53
56
  * @type {Map<string, number>}
54
57
  */
55
58
  const suppressed = new Map();
@@ -59,19 +62,41 @@ const suppressed = new Map();
59
62
  * ısıtmanın user-agent'ıyla geldiğinde doğru; gerçek trafiğin hataları her
60
63
  * zaman loglanmaya devam eder.
61
64
  *
62
- * @param {{ get?: (name: string) => string | undefined }} req
65
+ * @param {{ get?: (name: string) => string | undefined } | null | undefined} req
63
66
  * @returns {boolean}
64
67
  */
65
68
  export function isPrewarmRequest(req) {
66
- if (!prewarmProgress.active) return false;
69
+ if (!prewarmProgress.active || !req) return false;
67
70
  const ua = req.get?.("user-agent");
68
71
  return Boolean(ua) && ua === getConfig().brand.prewarmUserAgent;
69
72
  }
70
73
 
71
74
  /**
72
- * Bastırılan bir hatayı sayaca ekler. Yığın izi saklanmaz: özet satırının
73
- * amacı "neyin bozulduğunu" göstermek, hatayı ayıklamak değil — tek bir yol
74
- * için ayrıntı isteyen dev paneli `entries` üzerinden bakar.
75
+ * Şu an işlenen istek ısıtma turuna mı ait? `req` elde olmayan derin
76
+ * katmanlar (render, cache) için: yanıt nesnesi istek bağlamında taşınıyor.
77
+ *
78
+ * @returns {boolean}
79
+ */
80
+ function inPrewarmRequest() {
81
+ if (!prewarmProgress.active) return false;
82
+ return isPrewarmRequest(getRequestContext()?.res?.req);
83
+ }
84
+
85
+ /**
86
+ * Isıtma turuna ait bir uyarıyı loglamak yerine sayar.
87
+ *
88
+ * @param {string} message Gruplama anahtarı; yol adı içermemeli.
89
+ * @returns {boolean} `true` ise sayıldı, çağıran taraf loglamamalı.
90
+ */
91
+ export function suppressForPrewarm(message) {
92
+ if (!inPrewarmRequest()) return false;
93
+ suppressed.set(message, (suppressed.get(message) ?? 0) + 1);
94
+ return true;
95
+ }
96
+
97
+ /**
98
+ * Bastırılan bir istek hatasını sayaca ekler. Yığın izi saklanmaz: özet
99
+ * satırının amacı "neyin bozulduğunu" göstermek, hatayı ayıklamak değil.
75
100
  *
76
101
  * @param {number} status
77
102
  * @param {unknown} error
@@ -439,7 +464,7 @@ export async function prewarm({ origin, quiet = false, paths: only }) {
439
464
  const total = ranked.reduce((sum, [, n]) => sum + n, 0);
440
465
 
441
466
  console.warn(
442
- `[prewarm] ${total} request error${total === 1 ? "" : "s"} were not logged individually:\n` +
467
+ `[prewarm] ${total} problem${total === 1 ? " was" : "s were"} not logged individually:\n` +
443
468
  shown.map(([message, n]) => ` ${n}× ${message}`).join("\n") +
444
469
  (rest ? `\n … ${rest} more in ${ranked.length - shown.length} other kinds` : ""),
445
470
  );
@@ -39,6 +39,7 @@ import * as html from "../views/helpers/html.js";
39
39
  import * as tags from "../views/helpers/tags.js";
40
40
  import { loadComponents } from "../views/components/loader.js";
41
41
  import { renderStatusPage } from "./status-page.js";
42
+ import { suppressForPrewarm } from "./prewarm.js";
42
43
 
43
44
  const isDev = process.env.NODE_ENV === "development";
44
45
 
@@ -530,10 +531,12 @@ async function produce(controller, ctx) {
530
531
  let failures = first.transient;
531
532
 
532
533
  for (let round = 1; round <= attempts; round += 1) {
533
- console.warn(
534
- `[render] ${ctx.pathname} returned notFound() while upstream is failing ` +
535
- `(${summarizeFailures(failures)}), retrying (${round}/${attempts})`,
536
- );
534
+ const retryDetail =
535
+ `returned notFound() while upstream is failing ` +
536
+ `(${summarizeFailures(failures)}), retrying (${round}/${attempts})`;
537
+ if (!suppressForPrewarm(retryDetail)) {
538
+ console.warn(`[render] ${ctx.pathname} ${retryDetail}`);
539
+ }
537
540
 
538
541
  // Beklemeden tekrar denemek rate limit'e girmiş bir API'de aynı 429'u
539
542
  // getirir; kısa bekleme hem pencerenin dönmesine şans verir hem de
@@ -558,10 +561,12 @@ async function produce(controller, ctx) {
558
561
 
559
562
  // Denemeler tükendi. 404 yerine 503: önbelleğe girmez, `Retry-After` taşır
560
563
  // ve bir sonraki istek gerçek içeriği üretebilir.
561
- console.warn(
562
- `[render] ${ctx.pathname} could not be produced, upstream is still failing ` +
563
- `(${summarizeFailures(failures)}), serving an uncached 503 instead of a 404`,
564
- );
564
+ const outageDetail =
565
+ `could not be produced, upstream is still failing ` +
566
+ `(${summarizeFailures(failures)}), serving an uncached 503 instead of a 404`;
567
+ if (!suppressForPrewarm(outageDetail)) {
568
+ console.warn(`[render] ${ctx.pathname} ${outageDetail}`);
569
+ }
565
570
 
566
571
  return {
567
572
  html: await renderStatusPage(503),
@@ -624,17 +629,22 @@ function hasUpstreamFailures(pathname) {
624
629
  const transient = failures.filter((failure) => isTransientStatus(failure.status));
625
630
  const permanent = failures.filter((failure) => !isTransientStatus(failure.status));
626
631
 
632
+ // Isıtma turu yüzlerce yolu tarıyor; aynı upstream arızası her yol için bir
633
+ // satır basınca tur özeti kayboluyor. Turun uyarıları sayılıp sonunda
634
+ // toplanır (bkz. `suppressForPrewarm`).
627
635
  if (permanent.length) {
628
- console.warn(
629
- `[render] ${pathname} was produced with missing data, upstream is failing permanently (${summarizeFailures(permanent)})`,
630
- );
636
+ const detail = `missing data, upstream is failing permanently (${summarizeFailures(permanent)})`;
637
+ if (!suppressForPrewarm(detail)) {
638
+ console.warn(`[render] ${pathname} was produced with ${detail}`);
639
+ }
631
640
  }
632
641
 
633
642
  if (!transient.length) return false;
634
643
 
635
- console.warn(
636
- `[render] ${pathname} was produced with missing data, not caching it (${summarizeFailures(transient)})`,
637
- );
644
+ const detail = `missing data, not caching it (${summarizeFailures(transient)})`;
645
+ if (!suppressForPrewarm(detail)) {
646
+ console.warn(`[render] ${pathname} was produced with ${detail}`);
647
+ }
638
648
 
639
649
  return true;
640
650
  }