Source: validated PRD
RepoGraph Side Project.mdv2.0 §4-8. This document is the maintainability contract for contributors.
repograph/
├── apps/
│ ├── web/ # Next.js 15 (App Router) — landing, overview, explorer UI
│ └── api/ # NestJS — GitHub integration, ingestion, graph queries
├── packages/
│ ├── graph-model/ # Pure TS types: GraphNode, GraphEdge, RelationshipType, confidence
│ ├── github-client/ # Hybrid GitHub GraphQL+REST client, pagination, ETag, token pool
│ ├── relationship-engine/ # Normalizes GitHub data → resolves identity → extracts relationships → dedup
│ ├── db/ # Prisma schema (Postgres), migrations
│ ├── config/ # Shared ESLint / TSConfig / Tailwind preset
│ └── ui/ # Design system tokens + primitives (award-level, delegated to agy)
├── docker-compose.yml
├── package.json (workspaces)
└── turbo.json
| Layer | Choice | Reason | When to reconsider |
|---|---|---|---|
| Frontend | Next.js 15 + Tailwind + XYFlow | SSR landing, App Router, design system; XYFlow for progressive graph | — |
| Backend | NestJS + Prisma + Redis + BullMQ | Job dedup, rate-limit governor, incremental sync; Prisma typed DB | Collapse to Next route handlers if single-container team (PRD §4.2) |
| DB | PostgreSQL + JSONB generic nodes/edges |
Sufficient for ≤1M edges, recursive CTE hops ~50ms; no migration for new types | Neo4j/AGE if depth 4+ or PageRank |
| Cache/Queue | Redis + BullMQ | Mandatory for token rotation, dedup, backoff | — |
// Adding a new node type: extend union, no DB migration for UI
export type GraphNodeType = "repository" | "issue" | "pull_request" | "user";
// Adding a new relationship: register in registry
export const RELATIONSHIP_REGISTRY = { authored: {...}, reviewed: {...} } as const;- Token provider:
TokenPoolinterface — PAT, GitHub App, orprocess.env.GITHUB_TOKEN - Cache layer:
ETagStore— Redis or in-memory - Swap without touching business logic.
- Add relation: create single file in
rules/<relation>.tsimplementingRelationshipRule - Identity resolution: canonical
user:github:<id>(seeidentity.ts)
- Theming via CSS vars + tailwind preset; dark/light, reduced-motion
- Primitives: Button, Card, Badge, Input — never style inline
GET /repositories/:id/graph?focus=issue:123&depth=1&timeWindow=30d&nodeTypes=issue,pr,user&limit=300
→ { nodes[], edges[], meta: { focus, depth, complete, ingestionState, cacheHit, truncated } }
400 if !focus
400 if depth>2
400 if limit>400
Cache: graph:{owner}:{repo}:{focus}:{depth}:{timeWindow}:{hash(filters)} TTL 10m
POST /repositories/resolve { url: "https://github.com/goharbor/harbor.git" }
→ { id, fullName, status, cached }
GET /repositories/:id/status → { state: discovered|indexing|partially_ready|ready|refreshing|partial_error, ingested, total, lastError }
- Token rotation, leaky bucket 1 req/s/token,
Retry-Afterhonor - Secondary limit accounting: 900 REST pts/min, 2000 GraphQL pts/min, 100 concurrent
- Redis
SETNX ingestion:lock:{repoId} EX 300+jobId = repo:fullName:etag
- Tier 1 eager edges:
authored, assigned, reviewed, review_requested, closes_via_keyword (confidence high/medium)→ materialized inedges - Tier 2 lazy:
mentioned, referenced→ fetched timeline on-demand for focus neighbors only - Evidence:
edge.evidence[] { source, url }rendered as one-sentence tooltip
- Frontend: viewport rendering, memoized nodes/edges, WebWorker layout,
maxNodes 400 - Backend: indexed queries, cached subgraphs, background jobs
- DB:
GIN(metadata),(repo_id,type),BRIN(timestamps)
core/(this repo): Apache-2.0 foreverenterprise/(private, teams, AI, history): separate license
See CONTRIBUTING.md for workflow.