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.

src/infrastructure/github/github-api.server.ts is the server-only module that owns all GitHub REST API communication. It exports two functions consumed by the rest of the application.

fetchPortfolio(username)

export async function fetchPortfolio(username: string): Promise<PortfolioSnapshot>
Fetches, assembles, and caches a complete PortfolioSnapshot for the given GitHub username. The function is designed to always return a valid snapshot — it never throws and never returns null. Execution path:
  1. Cache check — calls readSnapshot(). If a fresh snapshot exists (within the 7-day TTL), it is returned immediately with no GitHub API calls.
  2. Parallel GitHub API calls — fires four requests concurrently via Promise.all:
    • GET /users/{username}GitHubUser
    • GET /users/{username}/repos?per_page=100&sort=updatedGitHubRepo[]
    • GET /users/{username}/social_accountsGitHubSocialAccount[]
    • GET /users/{username}/events/public?per_page=30GitHubEvent[]
  3. Degraded path — if the user call returns null (GitHub rate-limited or unreachable), readSnapshotOrStale() is tried first. If even a stale snapshot is unavailable, degradedSnapshot(username) is returned as the final fallback.
  4. Repository enrichment — forked repositories are filtered out. The remaining repos are passed through buildRepository() with a maximum concurrency of 3 (via mapLimit) to avoid overwhelming GitHub’s per-token rate limit.
  5. Snapshot assembly — a Profile, ActivityItem[] (up to 8 entries), and PortfolioStats (including language breakdown from computeLanguages) are built from the API responses.
  6. Cache write — if the snapshot is not degraded (i.e., all primary data was fetched successfully), writeSnapshot() is called and both cache layers are populated. The assembled snapshot is then returned.

invalidatePortfolioCache()

export function invalidatePortfolioCache()
Delegates directly to invalidateSnapshot() from the snapshot-cache module. Clears both the in-memory store and the Cloudflare Cache API entry so the next call to fetchPortfolio() performs a full re-fetch. This function is called at the start of every POST /api/public/sync request, before fetchPortfolio() is invoked, ensuring the response always reflects the latest data from GitHub.

Internal Helpers

The following functions are not exported. They are documented here for contributors working on the infrastructure layer.

get<T>(path, attempts)

async function get<T>(path: string, attempts = 3): Promise<T | null>
Retrying HTTP client for the GitHub REST API. Defaults to 3 attempts with exponential backoff starting at 500 ms (500 * 2^attempt). Retries on HTTP 403, 429, and any 5xx status, as well as network-level errors. Returns null after all attempts are exhausted, or immediately on any other 4xx status.

mapLimit<T, R>(items, limit, fn)

async function mapLimit<T, R>(
  items: T[],
  limit: number,
  fn: (item: T) => Promise<R>,
): Promise<R[]>
Concurrency-limited Promise.all. Spawns limit worker coroutines that each pull from a shared index until all items are processed. Used in fetchPortfolio with limit = 3 to enrich repositories without saturating the GitHub API.

buildRepository(raw)

async function buildRepository(raw: GitHubRepo): Promise<Repository>
Enriches a raw GitHubRepo API response into the domain Repository type. Makes two parallel requests for each repo with attempts = 1 (fast-fail):
  • GET /repos/{full_name}/languages → byte counts per language
  • GET /repos/{full_name}/releases/latest → latest ReleaseInfo
Runs detectTechnologies() on the combined name, description, topics, languages, and homepage, then maps all fields (stars, forks, license, topics, etc.) to the domain model.

computeLanguages(repos)

function computeLanguages(repos: Repository[]): LanguageSlice[]
Aggregates language byte counts across all repositories, computes each language’s percentage of total bytes, and returns the top 8 LanguageSlice[] sorted by descending byte count. Repo-count per language is tracked separately for display.

socialsFrom(user, extra)

function socialsFrom(user: GitHubUser, extra: GitHubSocialAccount[]): SocialLink[]
Builds a deduplicated SocialLink[] from multiple sources: the GitHub profile’s blog field, twitter_username, the /social_accounts endpoint, and the profile email. Each URL is run through safeExternalUrl and matched against a pattern map to assign a kind (linkedin, x, instagram, facebook, youtube, blog, or website). Duplicate URLs are removed before the array is returned.

safeExternalUrl(value)

function safeExternalUrl(value: string | null | undefined): string | null
Sanitises URLs before storing or rendering them. Accepts strings with or without a scheme (bare domains are prefixed with https://). Returns null for any URL whose parsed protocol is not http: or https:, blocking javascript:, data:, and similar schemes.

describeEvent(event)

function describeEvent(event: GitHubEvent): string
Maps a GitHub event type string to a human-readable Spanish summary. Handled event types: PushEvent, CreateEvent, ReleaseEvent, WatchEvent, ForkEvent, PullRequestEvent, IssuesEvent. Any unrecognised type falls back to the type name with the trailing Event suffix stripped.

degradedSnapshot(username)

function degradedSnapshot(username: string): PortfolioSnapshot
Returns an empty but structurally valid PortfolioSnapshot with degraded: true. All numeric stats are 0, all arrays are empty, and the profile fields are populated with minimal data derived from the username alone (e.g. avatar URL from avatars.githubusercontent.com). This allows UI components to render gracefully without conditional null checks.
This module must never be imported from client-side code. The .server.ts filename suffix is a build-time guard — any client import will cause a Vite build error. All consumer code accesses GitHub data through the getPortfolio server function in src/lib/portfolio.functions.ts.

Build docs developers (and LLMs) love