koa-classic-server 2.6.0 → 3.0.0-alpha.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 (40) hide show
  1. package/README.md +68 -10
  2. package/__tests__/caching-headers.test.js +30 -30
  3. package/__tests__/compression-fixtures/data.json +1 -0
  4. package/__tests__/compression-fixtures/large.txt +1 -0
  5. package/__tests__/compression-fixtures/small.txt +1 -0
  6. package/__tests__/compression.test.js +270 -0
  7. package/__tests__/customTest/serversToLoad.util.js +1 -1
  8. package/__tests__/deprecation-warnings.test.js +71 -183
  9. package/__tests__/dt-unknown.test.js +635 -0
  10. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +1 -0
  11. package/__tests__/hidden-fixtures/.env +2 -0
  12. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +1 -0
  13. package/__tests__/hidden-fixtures/config/secrets/password.txt +1 -0
  14. package/__tests__/hidden-fixtures/data.key +1 -0
  15. package/__tests__/hidden-fixtures/file.secret +1 -0
  16. package/__tests__/hidden-fixtures/index.html +1 -0
  17. package/__tests__/hidden-fixtures/normal.txt +1 -0
  18. package/__tests__/hidden-fixtures/subdir/.env +1 -0
  19. package/__tests__/hidden-fixtures/subdir/regular.txt +1 -0
  20. package/__tests__/hidden-option.test.js +422 -0
  21. package/__tests__/index-option.test.js +18 -16
  22. package/__tests__/index.test.js +8 -4
  23. package/__tests__/range-fixtures/sample.txt +1 -0
  24. package/__tests__/range.test.js +223 -0
  25. package/__tests__/security-headers.test.js +153 -0
  26. package/__tests__/security.test.js +145 -159
  27. package/__tests__/server-cache-fixtures/large.txt +1 -0
  28. package/__tests__/server-cache-fixtures/small.txt +1 -0
  29. package/__tests__/server-cache.test.js +423 -0
  30. package/__tests__/symlink.test.js +8 -5
  31. package/docs/ACTION_PLAN.md +293 -0
  32. package/docs/CHANGELOG.md +118 -0
  33. package/docs/EXAMPLES_INDEX_OPTION.md +2 -2
  34. package/docs/FLOW_DIAGRAM.md +13 -13
  35. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +864 -0
  36. package/docs/PERFORMANCE_COMPARISON.md +7 -7
  37. package/eslint.config.mjs +17 -0
  38. package/index.cjs +1114 -378
  39. package/index.mjs +1 -5
  40. package/package.json +4 -1
@@ -0,0 +1,293 @@
1
+ # Piano d'Azione — koa-classic-server
2
+
3
+ > Documento di lavoro per il miglioramento progressivo del progetto.
4
+ > Ogni fase segue il ciclo: **Descrizione → Soluzioni proposte → Domande → Implementazione → Test → Avanzamento**
5
+
6
+ ---
7
+
8
+ ## Indice delle fasi
9
+
10
+ | # | Fase | Priorità | Stato |
11
+ |---|------|----------|-------|
12
+ | 1 | [File nascosti nel directory listing](#fase-1--file-nascosti-nel-directory-listing) | Alta | ⬜ Da fare |
13
+ | 2 | [Opzione glob per escludere file sensibili](#fase-2--opzione-glob-per-escludere-file-sensibili) | Alta | ⬜ Da fare |
14
+ | 3 | [Content-Security-Policy nell'HTML generato](#fase-3--content-security-policy-nellhtml-generato) | Media | ⬜ Da fare |
15
+ | 4 | [Aggiunta ESLint e standardizzazione `===`](#fase-4--aggiunta-eslint-e-standardizzazione-) | Media | ⬜ Da fare |
16
+ | 5 | [Range Requests HTTP 206](#fase-5--range-requests-http-206) | Media | ⬜ Da fare |
17
+ | 6 | [Gzip / Brotli compression](#fase-6--gzip--brotli-compression) | Media | ⬜ Da fare |
18
+ | 7 | [Traduzione commenti in inglese nei test](#fase-7--traduzione-commenti-in-inglese-nei-test) | Bassa | ⬜ Da fare |
19
+ | 8 | [Roadmap rimozione opzioni deprecate (v3.0.0)](#fase-8--roadmap-rimozione-opzioni-deprecate-v300) | Bassa | ⬜ Da fare |
20
+
21
+ ---
22
+
23
+ > **Legenda stato:** ⬜ Da fare · 🔄 In corso · ✅ Completato · ⏸️ In pausa
24
+
25
+ ---
26
+
27
+ ## Processo standard per ogni fase
28
+
29
+ Per ogni fase si segue sempre questo ciclo:
30
+
31
+ 1. **Descrizione** — Spiegazione del problema e del suo impatto
32
+ 2. **Soluzioni proposte** — Almeno 2 alternative valutate con pro/contro
33
+ 3. **Domande** — Se le informazioni disponibili sono < 90%, si chiedono chiarimenti prima di procedere
34
+ 4. **Implementazione** — Modifica del codice sul branch `claude/project-review-EN6kt`
35
+ 5. **Test** — Esecuzione della suite di test e verifica regressioni
36
+ 6. **Avanzamento** — Aggiornamento dello stato in questo file e passaggio alla fase successiva
37
+
38
+ ---
39
+
40
+ ## Fase 1 — File nascosti nel directory listing
41
+
42
+ **Stato:** ⬜ Da fare
43
+ **Priorità:** Alta
44
+ **File coinvolti:** `index.cjs` (funzione `show_dir`)
45
+
46
+ ### Descrizione del problema
47
+
48
+ Il directory listing mostra **tutti** i file e le cartelle presenti nella webroot, inclusi quelli che iniziano con `.` (dot files), come:
49
+
50
+ - `.env` — variabili d'ambiente con credenziali
51
+ - `.gitignore`, `.gitattributes` — metadati Git
52
+ - `.htpasswd` — password Apache
53
+ - `.DS_Store` — metadati macOS
54
+ - `.npmrc` — configurazioni npm con token
55
+
56
+ Server come Apache e Nginx nascondono questi file per default. Un file dimenticato nella webroot potrebbe esporre informazioni sensibili agli utenti del browser.
57
+
58
+ ### Soluzioni proposte
59
+
60
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
61
+
62
+ ### Domande
63
+
64
+ *Da porre prima dell'implementazione se necessario.*
65
+
66
+ ### Implementazione
67
+
68
+ *Da eseguire dopo la scelta della soluzione.*
69
+
70
+ ### Test
71
+
72
+ *Da verificare dopo l'implementazione.*
73
+
74
+ ---
75
+
76
+ ## Fase 2 — Opzione glob per escludere file sensibili
77
+
78
+ **Stato:** ⬜ Da fare
79
+ **Priorità:** Alta
80
+ **File coinvolti:** `index.cjs` (opzioni di configurazione + `show_dir`)
81
+
82
+ ### Descrizione del problema
83
+
84
+ Non esiste un'opzione per escludere file o cartelle specifici dal serving e dal directory listing tramite pattern (glob o RegExp). Se un file come `config.json`, `secrets.yaml` o qualsiasi altro file sensibile si trova nella webroot, viene servito senza restrizioni.
85
+
86
+ Esempi di pattern utili:
87
+ - `*.env` — tutti i file .env
88
+ - `**/*.secret` — file con estensione .secret
89
+ - `private/**` — intera cartella private
90
+
91
+ ### Soluzioni proposte
92
+
93
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
94
+
95
+ ### Domande
96
+
97
+ *Da porre prima dell'implementazione se necessario.*
98
+
99
+ ### Implementazione
100
+
101
+ *Da eseguire dopo la scelta della soluzione.*
102
+
103
+ ### Test
104
+
105
+ *Da verificare dopo l'implementazione.*
106
+
107
+ ---
108
+
109
+ ## Fase 3 — Content-Security-Policy nell'HTML generato
110
+
111
+ **Stato:** ⬜ Da fare
112
+ **Priorità:** Media
113
+ **File coinvolti:** `index.cjs` (funzione `show_dir`)
114
+
115
+ ### Descrizione del problema
116
+
117
+ Le pagine HTML generate dal directory listing non includono un header `Content-Security-Policy`. Senza CSP, il browser non ha indicazioni su quali risorse sono autorizzate, aumentando il rischio di attacchi XSS in caso di eventuali vulnerabilità future nel codice di generazione HTML.
118
+
119
+ Il progetto già implementa l'escape HTML (funzione `escapeHtml`), ma aggiungere CSP è un ulteriore livello di difesa in profondità.
120
+
121
+ ### Soluzioni proposte
122
+
123
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
124
+
125
+ ### Domande
126
+
127
+ *Da porre prima dell'implementazione se necessario.*
128
+
129
+ ### Implementazione
130
+
131
+ *Da eseguire dopo la scelta della soluzione.*
132
+
133
+ ### Test
134
+
135
+ *Da verificare dopo l'implementazione.*
136
+
137
+ ---
138
+
139
+ ## Fase 4 — Aggiunta ESLint e standardizzazione `===`
140
+
141
+ **Stato:** ⬜ Da fare
142
+ **Priorità:** Media
143
+ **File coinvolti:** `index.cjs`, `package.json`, nuovi file di config
144
+
145
+ ### Descrizione del problema
146
+
147
+ Il codice usa `==` (uguaglianza debole con coercizione di tipo) in circa 25 punti, documentati in `docs/CODE_REVIEW.md`. Anche se nel contesto attuale non causano bug, l'uso di `===` è la best practice JavaScript e previene errori sottili in futuro.
148
+
149
+ Inoltre il progetto non ha alcun linter o formatter configurato, il che rende difficile mantenere la coerenza stilistica nel tempo e nelle contribuzioni esterne.
150
+
151
+ ### Soluzioni proposte
152
+
153
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
154
+
155
+ ### Domande
156
+
157
+ *Da porre prima dell'implementazione se necessario.*
158
+
159
+ ### Implementazione
160
+
161
+ *Da eseguire dopo la scelta della soluzione.*
162
+
163
+ ### Test
164
+
165
+ *Da verificare dopo l'implementazione.*
166
+
167
+ ---
168
+
169
+ ## Fase 5 — Range Requests HTTP 206
170
+
171
+ **Stato:** ⬜ Da fare
172
+ **Priorità:** Media
173
+ **File coinvolti:** `index.cjs` (logica di file serving)
174
+
175
+ ### Descrizione del problema
176
+
177
+ Il middleware non supporta l'header `Range` delle richieste HTTP. Questo significa che:
178
+
179
+ - File audio e video non possono essere riprodotti in streaming dai browser
180
+ - Download di file grandi non possono essere ripresi dopo interruzione
181
+ - Strumenti come `curl --range` non funzionano
182
+
183
+ Il supporto HTTP 206 (Partial Content) è lo standard per la distribuzione di contenuti multimediali e file di grandi dimensioni.
184
+
185
+ ### Soluzioni proposte
186
+
187
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
188
+
189
+ ### Domande
190
+
191
+ *Da porre prima dell'implementazione se necessario.*
192
+
193
+ ### Implementazione
194
+
195
+ *Da eseguire dopo la scelta della soluzione.*
196
+
197
+ ### Test
198
+
199
+ *Da verificare dopo l'implementazione.*
200
+
201
+ ---
202
+
203
+ ## Fase 6 — Gzip / Brotli compression
204
+
205
+ **Stato:** ⬜ Da fare
206
+ **Priorità:** Media
207
+ **File coinvolti:** `index.cjs`, `package.json`
208
+
209
+ ### Descrizione del problema
210
+
211
+ Il middleware serve tutti i file senza compressione. Per file di testo (HTML, CSS, JS, JSON, SVG), la compressione può ridurre le dimensioni del 60-80%, con impatto diretto su:
212
+
213
+ - Velocità di caricamento per gli utenti
214
+ - Consumo di banda del server
215
+ - Score Lighthouse / Web Vitals
216
+
217
+ ### Soluzioni proposte
218
+
219
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
220
+
221
+ ### Domande
222
+
223
+ *Da porre prima dell'implementazione se necessario.*
224
+
225
+ ### Implementazione
226
+
227
+ *Da eseguire dopo la scelta della soluzione.*
228
+
229
+ ### Test
230
+
231
+ *Da verificare dopo l'implementazione.*
232
+
233
+ ---
234
+
235
+ ## Fase 7 — Traduzione commenti in inglese nei test
236
+
237
+ **Stato:** ⬜ Da fare
238
+ **Priorità:** Bassa
239
+ **File coinvolti:** `__tests__/dt-unknown.test.js` e altri
240
+
241
+ ### Descrizione del problema
242
+
243
+ Alcuni file di test contengono commenti in italiano, il che riduce l'accessibilità del codice per i contribuenti internazionali. Per un progetto open source pubblicato su npm, la lingua standard della codebase dovrebbe essere l'inglese.
244
+
245
+ ### Soluzioni proposte
246
+
247
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
248
+
249
+ ### Domande
250
+
251
+ *Da porre prima dell'implementazione se necessario.*
252
+
253
+ ### Implementazione
254
+
255
+ *Da eseguire dopo la scelta della soluzione.*
256
+
257
+ ### Test
258
+
259
+ *Da verificare dopo l'implementazione.*
260
+
261
+ ---
262
+
263
+ ## Fase 8 — Roadmap rimozione opzioni deprecate (v3.0.0)
264
+
265
+ **Stato:** ⬜ Da fare
266
+ **Priorità:** Bassa
267
+ **File coinvolti:** `index.cjs`, `docs/CHANGELOG.md`, `README.md`
268
+
269
+ ### Descrizione del problema
270
+
271
+ Le opzioni legacy `cacheMaxAge` ed `enableCaching` sono ancora supportate con deprecation warning ma non hanno una data di rimozione pianificata. Mantenerle a tempo indeterminato appesantisce il codice e può confondere i nuovi utenti che leggono la documentazione.
272
+
273
+ Una roadmap chiara verso `v3.0.0` con breaking changes definiti aiuta gli utenti a pianificare la migrazione.
274
+
275
+ ### Soluzioni proposte
276
+
277
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
278
+
279
+ ### Domande
280
+
281
+ *Da porre prima dell'implementazione se necessario.*
282
+
283
+ ### Implementazione
284
+
285
+ *Da eseguire dopo la scelta della soluzione.*
286
+
287
+ ### Test
288
+
289
+ *Da verificare dopo l'implementazione.*
290
+
291
+ ---
292
+
293
+ *Documento aggiornato: 2026-03-21*
package/docs/CHANGELOG.md CHANGED
@@ -5,6 +5,124 @@ All notable changes to koa-classic-server will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.0.0] - Unreleased
9
+
10
+ ### 🆕 New Features
11
+
12
+ #### `hidden` option — protect dot-files, dot-dirs and custom patterns (Fase 1 + Fase 2)
13
+
14
+ A new `hidden` option controls which files and directories are blocked from both directory listing and direct HTTP access (returning **404**). Applies recursively to the entire directory tree.
15
+
16
+ **Default behavior (secure out of the box):**
17
+ - Dot-files (names starting with `.`) → **hidden by default** (`dotFiles.default: 'hidden'`)
18
+ - Dot-directories → **visible by default** (`dotDirs.default: 'visible'`)
19
+
20
+ ```js
21
+ app.use(koaClassicServer('/public', {
22
+ hidden: {
23
+ dotFiles: {
24
+ default: 'hidden', // 'hidden' | 'visible'
25
+ whitelist: ['.well-known'], // Always visible (string exact/glob or RegExp)
26
+ blacklist: [], // Always hidden — overrides whitelist
27
+ },
28
+ dotDirs: {
29
+ default: 'visible',
30
+ whitelist: [],
31
+ blacklist: ['.git', /^\.svn/],
32
+ },
33
+ alwaysHide: ['*.secret', 'config/secrets/**', /\.key$/], // Path-aware patterns
34
+ }
35
+ }));
36
+ ```
37
+
38
+ **Priority (highest to lowest):**
39
+ 1. `blacklist` — always hidden, beats everything
40
+ 2. `whitelist` — always visible, overrides `alwaysHide` and `default`
41
+ 3. `alwaysHide` — path-aware patterns, beats `default`
42
+ 4. `default` — fallback behavior for unmatched dot-entries
43
+
44
+ **`alwaysHide` pattern rules:**
45
+ - String without `/`: matches basename at any depth (e.g. `*.secret` hides `a/b/file.secret`)
46
+ - String with `/`: path-anchored from root (e.g. `config/secrets/**`)
47
+ - RegExp: tested against the full relative path
48
+
49
+ **Blocked dot-dirs block sub-paths too:**
50
+ `GET /.git/config` returns 404 if `.git` is in `dotDirs.blacklist`.
51
+
52
+ ### ⚠️ Breaking Changes
53
+
54
+ #### Dot-files hidden by default
55
+ `hidden.dotFiles.default` is `'hidden'` by default. In v2.x, dot-files (`.env`, `.gitignore`, etc.) were served normally. In v3.x they return 404 unless explicitly allowed.
56
+
57
+ To restore v2.x behavior: `hidden: { dotFiles: { default: 'visible' } }`
58
+
59
+ #### Removed string format for `index` option
60
+ - **Removed**: `index: 'index.html'` — passing a non-empty string now throws an `Error` at startup
61
+ - **Empty string** `index: ''` is still silently treated as `[]` (no index file, show directory listing)
62
+ - **Migration**:
63
+ ```js
64
+ // Before (v2.x — now throws)
65
+ app.use(koaClassicServer('/public', { index: 'index.html' }));
66
+
67
+ // After (v3.0.0)
68
+ app.use(koaClassicServer('/public', { index: ['index.html'] }));
69
+ ```
70
+
71
+ #### Removed deprecated option names `cacheMaxAge` and `enableCaching`
72
+ - **Removed**: `cacheMaxAge` — use `browserCacheMaxAge` instead
73
+ - **Removed**: `enableCaching` — use `browserCacheEnabled` instead
74
+ - **Behaviour**: Passing either removed option now throws an `Error` at startup with a clear migration message pointing to the new name and the current value.
75
+ - **Migration**:
76
+ ```js
77
+ // Before (v2.x — now throws)
78
+ app.use(koaClassicServer('/public', {
79
+ enableCaching: true,
80
+ cacheMaxAge: 3600
81
+ }));
82
+
83
+ // After (v3.0.0)
84
+ app.use(koaClassicServer('/public', {
85
+ browserCacheEnabled: true,
86
+ browserCacheMaxAge: 3600
87
+ }));
88
+ ```
89
+
90
+ ---
91
+
92
+ ## [2.6.1] - 2026-03-04
93
+
94
+ ### 🐛 Bug Fix
95
+
96
+ #### Fixed DT_UNKNOWN Handling (type 0) on overlayfs, NFS, FUSE, NixOS buildFHSEnv, ecryptfs
97
+ - **Issue**: On filesystems where `readdir({ withFileTypes: true })` returns dirents with `DT_UNKNOWN` (type 0), all `dirent.is*()` methods return `false`. This caused three failures:
98
+ 1. `isFileOrSymlinkToFile()` missed valid files — `findIndexFile()` returned empty results, so `GET /` showed a directory listing instead of rendering the index file
99
+ 2. `isDirOrSymlinkToDir()` missed valid directories — directory type resolution failed
100
+ 3. `show_dir()` skipped entries with type 0, logging `"Unknown file type: 0"` — directory listings appeared empty or partial
101
+ - **Affected environments**: overlayfs (Docker image layers), NFS (some implementations), FUSE filesystems (sshfs, s3fs, rclone mount), NixOS with buildFHSEnv, ecryptfs (encrypted home directories), and any filesystem that doesn't fill `d_type` in the kernel's `getdents64` syscall
102
+ - **Impact**: HIGH — Server unusable on affected filesystems (index file not served, directory listing empty)
103
+ - **Fix**: Added `fs.promises.stat()` fallback in all three locations when none of the `dirent.is*()` type methods return `true` (i.e., type is genuinely unknown). On standard filesystems (ext4, btrfs, xfs, APFS, NTFS), `d_type` is always filled correctly, so the `stat()` fallback is never reached — **zero performance overhead** on the fast path.
104
+ - **Code**:
105
+ - `isFileOrSymlinkToFile()` — DT_UNKNOWN fallback via `stat().isFile()`
106
+ - `isDirOrSymlinkToDir()` — DT_UNKNOWN fallback via `stat().isDirectory()`
107
+ - `show_dir()` — Accept type 0 entries and resolve via `stat()` instead of skipping them
108
+ - **Reference**: Linux `man 2 getdents` — *"Currently, only some filesystems have full support for returning the file type in d_type. All applications must properly handle a return of DT_UNKNOWN."*
109
+
110
+ ### 🧪 Testing
111
+ - Added `__tests__/dt-unknown.test.js` with 20 tests covering:
112
+ - `isFileOrSymlinkToFile` / `isDirOrSymlinkToDir` with DT_UNKNOWN dirents
113
+ - `findIndexFile` with all-unknown-type entries (string and RegExp patterns)
114
+ - `show_dir` rendering (resolved types, no skipped entries, correct MIME types and sizes)
115
+ - Full integration tests (index file serving, direct file access, complete directory listing)
116
+ - Edge cases (mixed regular + DT_UNKNOWN dirents, index priority, Dirent type 0 verification)
117
+ - Tests use `jest.spyOn(fs.promises, 'readdir')` to mock DT_UNKNOWN dirents via `new fs.Dirent(name, 0)` while keeping `fs.promises.stat()` working normally
118
+ - All 329 tests pass across 12 test suites (zero regressions)
119
+
120
+ ### 📦 Package Changes
121
+ - **Version**: `2.6.0` → `2.6.1`
122
+ - **Semver**: Patch version bump (bug fix only, no API changes)
123
+
124
+ ---
125
+
8
126
  ## [2.6.0] - 2026-03-01
9
127
 
10
128
  ### 📦 Dependency Upgrades
@@ -359,8 +359,8 @@ app.use(koaClassicServer('./public', {
359
359
  ],
360
360
 
361
361
  // HTTP Caching
362
- enableCaching: true,
363
- cacheMaxAge: 3600,
362
+ browserCacheEnabled: true,
363
+ browserCacheMaxAge: 3600,
364
364
 
365
365
  // URL configuration
366
366
  urlPrefix: '/static',
@@ -141,13 +141,13 @@ module.exports = function koaClassicServer(rootDir, opts = {}) {
141
141
  : [];
142
142
 
143
143
  // 7. Configure HTTP caching
144
- options.cacheMaxAge = typeof options.cacheMaxAge === 'number' &&
145
- options.cacheMaxAge >= 0
146
- ? options.cacheMaxAge
144
+ options.browserCacheMaxAge = typeof options.browserCacheMaxAge === 'number' &&
145
+ options.browserCacheMaxAge >= 0
146
+ ? options.browserCacheMaxAge
147
147
  : 3600;
148
- options.enableCaching = typeof options.enableCaching === 'boolean'
149
- ? options.enableCaching
150
- : true;
148
+ options.browserCacheEnabled = typeof options.browserCacheEnabled === 'boolean'
149
+ ? options.browserCacheEnabled
150
+ : false;
151
151
 
152
152
  // 8. Return Koa middleware function
153
153
  return async (ctx, next) => {
@@ -172,8 +172,8 @@ START
172
172
  │ ├─ urlsReserved: []
173
173
  │ ├─ template.render: undefined
174
174
  │ ├─ template.ext: []
175
- │ ├─ cacheMaxAge: 3600 (1 hour)
176
- │ └─ enableCaching: true
175
+ │ ├─ browserCacheMaxAge: 3600 (1 hour)
176
+ │ └─ browserCacheEnabled: false
177
177
 
178
178
  └─> Return async middleware function
179
179
  ```
@@ -415,7 +415,7 @@ Handles serving individual files with caching, template rendering, and streaming
415
415
 
416
416
 
417
417
  ┌─────────────────────────────────────────────────────────────────┐
418
- │ 3. HTTP Caching (if enableCaching = true)
418
+ │ 3. HTTP Caching (if browserCacheEnabled = true)
419
419
  │ Generate ETag: "mtime-size" │
420
420
  │ Set Last-Modified: mtime.toUTCString() │
421
421
  │ Set Cache-Control: public, max-age=3600 │
@@ -492,7 +492,7 @@ fileExt in template.ext? → YES
492
492
  ### Code Example: HTTP Caching
493
493
  ```javascript
494
494
  // index.cjs:313-350
495
- if (options.enableCaching) {
495
+ if (options.browserCacheEnabled) {
496
496
  // Generate ETag
497
497
  const etag = `"${fileStat.mtime.getTime()}-${fileStat.size}"`;
498
498
 
@@ -502,7 +502,7 @@ if (options.enableCaching) {
502
502
  // Set headers
503
503
  ctx.set('ETag', etag);
504
504
  ctx.set('Last-Modified', lastModified);
505
- ctx.set('Cache-Control', `public, max-age=${options.cacheMaxAge}, must-revalidate`);
505
+ ctx.set('Cache-Control', `public, max-age=${options.browserCacheMaxAge}, must-revalidate`);
506
506
 
507
507
  // Check If-None-Match (ETag validation)
508
508
  const clientEtag = ctx.get('If-None-Match');
@@ -782,8 +782,8 @@ app.use(koaClassicServer('/var/www/public', {
782
782
  },
783
783
 
784
784
  // HTTP caching (1 hour)
785
- cacheMaxAge: 3600,
786
- enableCaching: true
785
+ browserCacheMaxAge: 3600,
786
+ browserCacheEnabled: true
787
787
  }));
788
788
 
789
789
  app.listen(3000);