OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-web-ui commits / adda29a

Add HTTP caching helpers: cacheControl, withEtag, jsonCached
- cacheControl(res, opts) — set Cache-Control (public, maxAge, immutable, noCache) - withEtag(req, res, etag, sendFn) — ETag + 304 Not Modified support - jsonCached(res, data, maxAge) — JSON response with short-lived cache header Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adda29aba7ab056418196d859222b8b161132584
by plankatron <fleeky@gmail.com> · 2026-03-30T17:49:15.000Z

Changed files (2) +53 -1

index.js +1 -1
……@@ -1,5 +1,5 @@
11// Server-side exports
2export { Router, json, html, readBody, readJson, parsePagination } from './lib/router.js'
2export { Router, json, html, readBody, readJson, parsePagination, cacheControl, withEtag, jsonCached } from './lib/router.js'
33export { authMiddleware, createChallenge, verifyChallenge } from './lib/auth.js'
44export { serveStatic } from './lib/static.js'
55export { renderMarkdown, renderDiff, renderCode, renderPagination, renderBranchSelector, detectLanguage } from './lib/render.js'
lib/router.js +52 -0
……@@ -166,3 +166,55 @@ function setCorsHeaders (res, origin) {
166166 res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
167167 res.setHeader('Access-Control-Max-Age', '86400')
168168}
169
170// ── HTTP Caching Helpers ─────────────────────────────────────
171
172/**
173 * Set Cache-Control header
174 * @param {Object} res
175 * @param {{ maxAge?: number, public?: boolean, immutable?: boolean, noCache?: boolean }} opts
176 */
177export function cacheControl (res, opts = {}) {
178 const parts = []
179 if (opts.noCache) { parts.push('no-cache', 'no-store'); res.setHeader('Cache-Control', parts.join(', ')); return }
180 if (opts.public !== false) parts.push('public')
181 if (opts.maxAge != null) parts.push(`max-age=${opts.maxAge}`)
182 if (opts.immutable) parts.push('immutable')
183 res.setHeader('Cache-Control', parts.join(', '))
184}
185
186/**
187 * Send response with ETag. Returns true if 304 Not Modified was sent.
188 * @param {Object} req
189 * @param {Object} res
190 * @param {string} etag — ETag value (without quotes)
191 * @param {Function} sendFn — called with (res) if content needs to be sent
192 * @returns {boolean} true if 304 was sent
193 */
194export function withEtag (req, res, etag, sendFn) {
195 const quoted = `"${etag}"`
196 res.setHeader('ETag', quoted)
197
198 const ifNoneMatch = req.headers['if-none-match']
199 if (ifNoneMatch === quoted) {
200 res.writeHead(304)
201 res.end()
202 return true
203 }
204
205 sendFn(res)
206 return false
207}
208
209/**
210 * Send JSON response with short-lived cache (for dynamic data that changes on push)
211 */
212export function jsonCached (res, data, maxAge = 10) {
213 const body = JSON.stringify(data)
214 res.writeHead(200, {
215 'Content-Type': 'application/json',
216 'Content-Length': Buffer.byteLength(body),
217 'Cache-Control': `public, max-age=${maxAge}`
218 })
219 res.end(body)
220}