← back to Wallco Ai
by-color: 5min in-memory TTL cache (FIFO 1k cap, sorted-exclude key) — same PDP rail hex now skips full 1515-design ΔE recompute
97ebc807ea9626a8a0e1fb8def7dd8cc75659b7c · 2026-05-12 18:19:49 -0700 · SteveStudio2
Files touched
Diff
commit 97ebc807ea9626a8a0e1fb8def7dd8cc75659b7c
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date: Tue May 12 18:19:49 2026 -0700
by-color: 5min in-memory TTL cache (FIFO 1k cap, sorted-exclude key) — same PDP rail hex now skips full 1515-design ΔE recompute
---
server.js | 33 +++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/server.js b/server.js
index 93bb72f..f9290c8 100644
--- a/server.js
+++ b/server.js
@@ -384,15 +384,36 @@ function _dE(a, b) {
const dL = a[0]-b[0], dA = a[1]-b[1], dB = a[2]-b[2];
return Math.sqrt(dL*dL + dA*dA + dB*dB);
}
+// In-memory TTL cache for /api/designs/by-color. The Lab conversion + ΔE sort
+// runs over every DESIGN on every call — the same PDP rail hex + size combo
+// gets hammered as users browse, so cache the JSON body for the same 5min
+// window the response already advertises in Cache-Control. Bounded to 1k keys
+// FIFO so memory can't grow unbounded; invalidated whenever DESIGNS is rebuilt
+// (callers should bump _byColorCacheVer if they mutate DESIGNS in place).
+const _BY_COLOR_TTL_MS = 300_000;
+const _BY_COLOR_MAX = 1000;
+const _byColorCache = new Map(); // key -> { body, expiresAt }
+let _byColorCacheVer = 0; // bump to invalidate
app.get('/api/designs/by-color', (req, res) => {
const hex = String(req.query.hex || '').trim();
const n = Math.max(1, Math.min(50, parseInt(req.query.n || '12', 10)));
- const exclude = new Set(String(req.query.exclude || '').split(',').map(s => parseInt(s.trim(),10)).filter(x => x>0));
+ const excludeArr = String(req.query.exclude || '').split(',').map(s => parseInt(s.trim(),10)).filter(x => x>0).sort((a,b)=>a-b);
+ const exclude = new Set(excludeArr);
// Optional ?within=category — restricts results to designs in this category.
// Useful for "more in this palette WITHIN damask" rails on a vertical PDP.
const within = String(req.query.within || '').toLowerCase().trim();
const target = _hexToLab(hex);
if (!target) return res.status(400).json({ ok:false, error:'invalid hex (try ?hex=%23c16d74 or hex=c16d74)' });
+ // Cache key sorts exclude so e.g. exclude=72,5 and exclude=5,72 hit the same entry.
+ const cacheKey = `v${_byColorCacheVer}|${hex.toLowerCase()}|${n}|${within}|${excludeArr.join(',')}`;
+ const now = Date.now();
+ const hit = _byColorCache.get(cacheKey);
+ if (hit && hit.expiresAt > now) {
+ res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
+ res.setHeader('X-Cache', 'HIT');
+ return res.json(hit.body);
+ }
+ if (hit) _byColorCache.delete(cacheKey); // expired — drop before recompute
const ranked = [];
for (const d of DESIGNS) {
if (exclude.has(d.id)) continue;
@@ -403,8 +424,16 @@ app.get('/api/designs/by-color', (req, res) => {
ranked.push({ id: d.id, title: d.title, category: d.category, handle: d.handle, image_url: d.image_url, dominant_hex: d.dominant_hex, room_mockups: d.room_mockups || [], delta_e: _dE(target, lab), url: `/design/${d.id}` });
}
ranked.sort((a,b) => a.delta_e - b.delta_e);
+ const body = { hex, n, within: within || null, count: ranked.length, exclude: [...exclude], results: ranked.slice(0, n).map(r => ({ ...r, delta_e: Math.round(r.delta_e * 10)/10 })) };
+ // FIFO evict if full. Map preserves insertion order, so the first key is the oldest.
+ if (_byColorCache.size >= _BY_COLOR_MAX) {
+ const firstKey = _byColorCache.keys().next().value;
+ if (firstKey !== undefined) _byColorCache.delete(firstKey);
+ }
+ _byColorCache.set(cacheKey, { body, expiresAt: now + _BY_COLOR_TTL_MS });
res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
- res.json({ hex, n, within: within || null, count: ranked.length, exclude: [...exclude], results: ranked.slice(0, n).map(r => ({ ...r, delta_e: Math.round(r.delta_e * 10)/10 })) });
+ res.setHeader('X-Cache', 'MISS');
+ res.json(body);
});
// ── /api/designs/random?n=12&category=floral&seed=42
← 66cfe47 drunk-animal overnight generator + /admin/reload-designs
·
back to Wallco Ai
·
drunk-animals: fix launchd PATH for PIL + retro-backfill 26 307a374 →