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 +8 -6
- package/docs/06-cache.md +7 -5
- package/docs/en/06-caching.md +7 -5
- package/package.json +1 -1
- package/src/server/prewarm.js +34 -9
- package/src/server/render.js +24 -14
package/CHANGELOG.md
CHANGED
|
@@ -87,12 +87,14 @@ one is listed under a **Breaking** heading.
|
|
|
87
87
|
|
|
88
88
|
### Changed
|
|
89
89
|
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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ı
|
|
710
|
-
|
|
711
|
-
|
|
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]
|
|
715
|
-
|
|
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
|
|
package/docs/en/06-caching.md
CHANGED
|
@@ -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
|
|
731
|
-
|
|
732
|
-
|
|
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]
|
|
736
|
-
|
|
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.
|
|
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",
|
package/src/server/prewarm.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
52
|
-
*
|
|
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
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
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}
|
|
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
|
);
|
package/src/server/render.js
CHANGED
|
@@ -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
|
-
|
|
534
|
-
`
|
|
535
|
-
|
|
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
|
-
|
|
562
|
-
`
|
|
563
|
-
|
|
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
|
-
|
|
629
|
-
|
|
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
|
-
|
|
636
|
-
|
|
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
|
}
|