Skip to content

Add opt-in cache of rendered images (lib + server) - #55

Open
devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/1788719406-render-cache
Open

devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/1788719406-render-cache

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Refs #11. The only cache so far is the per-page tile network cache in lib.js; every request still drives the headless browser. This adds an opt-in cache of the rendered images, keyed by a stable hash of the render-affecting options, so a map used as <img src="..."> is rendered once and then served from memory/disk.

src/cache.js (new, no new dependencies)

  • cacheKey(normalizedOptions) — sha256 of a stable-stringified whitelist of render-affecting options (geojson, geojsonfile, width, height, ..., type, quality). timeout, haltOnConsoleError, renderToHtml and unknown params don't influence the key.
  • RenderCache({ max = 100, ttl = 3600, dir }) — in-memory LRU bounded by max entries with a TTL in seconds; when dir is set, entries are also written as <dir>/<key>.bin and read back on a memory miss (expiry by file mtime, lazily unlinked). max: 0 + dir = disk-only. clear() only removes *.bin files in dir.

src/lib.js

export function configureCache(opts)  // { max, ttl, dir } | null/false → RenderCache | null (active if max > 0 || dir)
export function getCache()
export function cacheKey(options)     // normalizes with the lib defaults, then hashes

The defaults block moved into normalizeOptions() so the same normalization backs both rendering and cacheKey (also coerces width/height/maxZoom/quality to numbers and defaults type to 'png', so ?width=800 and no width share an entry). In the render path: on a key hit the cached buffer is resolved before touching the browser; each of the three resolve(...) image exits stores through store() (fire-and-forget, errors logged to stderr). renderToHtml output and geojsonfile: '-' (stdin) are never cached. Cache is off unless configureCache is called — default behaviour unchanged.

src/server.js — when a cache is active and the response is an image:

  • Cache-Control: public, max-age=<ttl>, ETag: "<key>", 304 via req.fresh on If-None-Match (no render), X-Cache: HIT|MISS.
  • handler now receives req.

src/cli.jsosmsm serve --cache-max <n> (default 0 = off), --cache-ttl <seconds> (default 3600), --cache-dir <path>; env fallbacks CACHE_MAX / CACHE_TTL / CACHE_DIR (matching the existing unprefixed PORT/HEADER_CHECK style). Logs Render cache enabled: ... at startup.

Also: src/cache.js added to the files whitelist, lib.d.ts (CacheOptions, RenderCache, new exports), README "Caching" section, CHANGELOG "Unreleased" entry.

Out of scope / possible follow-ups: redis/memcache backends, a client-controlled cache_force_refresh param (would let any client bypass the cache), eager sweeping of expired files in dir, bounding the disk size.

Tests

  • test/cache.test.js — key stability/normalization/exclusions, LRU eviction, TTL expiry (fake timers), max: 0, directory persistence across instances, mtime-based disk expiry, clear/delete on disk.
  • test/render-cache.test.js — real browser: second identical render makes zero tile requests and returns an identical buffer; different options re-render; renderToHtml not cached; server X-Cache MISS→HIT, ETag/Cache-Control, 304 on matching If-None-Match, no headers when disabled, /dynamic never cached.
  • npm test: 36 passed (4 files) on Node 24. Manual osmsm serve --cache-max 5 --cache-dir /tmp/osmsm-cache + two curls: MISS then HIT, .bin written, identical bytes.

Link to Devin session: https://app.devin.ai/sessions/d2cb73fa9fb948da9625b7f61d0103d1
Open in Devin Desktop: https://app.devin.ai/desktop/session/d2cb73fa9fb948da9625b7f61d0103d1?variant=devin
Requested by: @jperelli

Refs #11

Co-Authored-By: Julian Perelli <jperelli@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant