Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/santiagonieto09/portafolio/llms.txt

Use this file to discover all available pages before exploring further.

Rather than hitting the GitHub REST API on every incoming request, the portfolio serialises the fully mapped PortfolioSnapshot into a two-layer cache. The in-memory layer gives near-zero latency reads within the same process. The Cloudflare Cache API layer persists the snapshot across cold starts and across the multiple Worker instances that may be running in the same datacenter. Together they ensure the portfolio loads instantly for visitors, stays within GitHub’s rate limits, and continues serving content even when GitHub is temporarily unavailable. All cache logic lives in src/infrastructure/github/snapshot-cache.ts.

Cache layers

In-memory (let memory)

A module-level variable holds the most recent snapshot as:
let memory: { at: number; value: PortfolioSnapshot } | null = null;
at is a Unix timestamp (milliseconds). This is the fastest read path — a synchronous comparison, no I/O. Its limitation is that it is scoped to a single process: a cold start or a new Worker isolate begins with memory = null.

Cloudflare Cache API (caches.default)

The snapshot is persisted as a JSON Response keyed on the synthetic URL https://portfolio.internal/snapshot. Cloudflare’s Cache API is shared across Worker instances within the same datacenter, so a cold-started isolate can warm its in-memory cache from the Cloudflare layer without calling GitHub. The response is stored with:
HeaderValue
content-typeapplication/json
cache-controlpublic, max-age=604800
x-snapshot-atUnix timestamp (ms) when the snapshot was written
The x-snapshot-at header is used for TTL validation on read, because the standard max-age may be modified or stripped by intermediate caching layers. When caches.default is unavailable (Node.js, local dev), all Cloudflare Cache API operations are skipped silently and the module falls back to in-memory only.

Read path

readSnapshot() returns a fresh snapshot or null:
  1. Check memory. If set and Date.now() - memory.at < WEEK_MS (7 × 24 × 60 × 60 × 1 000 ms), return memory.value.
  2. Call readFromCacheApi() which matches https://portfolio.internal/snapshot against caches.default, reads the x-snapshot-at header, and applies the same 7-day TTL check.
  3. On a Cloudflare Cache hit, back-fill memory so subsequent reads in the same process skip the Cache API entirely.
  4. Return null if both layers miss or return expired data.

Stale reads

readSnapshotOrStale() is used by the degraded-mode fallback path:
export async function readSnapshotOrStale(): Promise<PortfolioSnapshot | null> {
  return (await readSnapshot()) ?? memory?.value ?? null;
}
It calls readSnapshot() first. If that returns null (expired or missing), it returns memory.value without the TTL check — serving the last known-good snapshot even if it is days old. This is deliberately chosen over an error state when GitHub is temporarily unavailable.

Write path

writeSnapshot(value) is called after a successful full fetch from GitHub:
  1. Sets memory = { at: Date.now(), value } immediately.
  2. Serialises value to JSON and calls cache.put(CACHE_URL, new Response(...)) with the headers described above.
  3. If the Cloudflare Cache API is unavailable, the put is skipped silently — the in-memory write always succeeds.

Invalidation

invalidateSnapshot() forces the next read to bypass the cache entirely:
  1. Sets memory = null.
  2. Calls cache.delete(CACHE_URL) on caches.default (no-op if unavailable).
It is called by invalidatePortfolioCache() in github-api.server.ts, which is the public invalidation API exposed to the sync endpoint.

Weekly refresh

A snapshot is considered fresh for 7 days. After that, the next request triggers a full re-fetch from GitHub. Forced early invalidation is triggered by POST /api/public/sync:
invalidatePortfolioCache(); // clears both in-memory and Cloudflare Cache API layers
const snapshot = await fetchPortfolio(GITHUB_USERNAME); // re-fetches all data from GitHub
The sync endpoint invalidates first to guarantee the subsequent fetchPortfolio call sees a cache miss, then immediately re-populates both cache layers with fresh data.
In Node.js and local development the Cloudflare Cache API is not available — caches.default is undefined. The cache module detects this at runtime and silently skips all Cloudflare Cache API operations. Snapshots are cached in memory for the lifetime of the process and reset on restart. To force a fresh fetch during development without restarting the server, call POST /api/public/sync with the CRON_SECRET set in your environment and the matching x-cron-secret header.

Build docs developers (and LLMs) love