Adds .planning/codebase/ with parallel-mapper outputs covering stack, integrations, architecture, structure, conventions, testing, and concerns.
30 KiB
Architecture
Analysis Date: 2026-05-11
System Overview
Reactive Resume is a single full-stack web application running as one Node.js process on port 3000. The web app is a TanStack Start application (Vite + React 19 + Nitro server) packaged in a pnpm/Turborepo monorepo. All API surface area, server-side rendering, file uploads, OAuth, OpenAPI, MCP, and PWA assets are served from the same process. Internal packages are consumed as TypeScript source through package.json exports maps that point directly at src files; there is no per-package dist output to depend on.
┌────────────────────────────────────────────────────────────────────────────┐
│ Browser (React 19) │
│ TanStack Router · TanStack Query · Zustand stores · React PDF view │
│ `apps/web/src/router.tsx` · `apps/web/src/routes/__root.tsx` │
└──────────┬─────────────────────────────────────────────────────┬───────────┘
│ HTTP / SSR hydration │ /api/rpc (RPCLink)
▼ ▼
┌────────────────────────────────────────────────────────────────────────────┐
│ Nitro server entry (TanStack Start) │
│ `apps/web/src/server.ts` · Nitro plugins (`apps/web/plugins/`) │
├──────────────────────────┬─────────────────────────────────────────────────┤
│ File-based routes │ Route `server.handlers` blocks │
│ `apps/web/src/routes/*` │ `api/rpc.$.ts` · `api/auth.$.ts` · `api/health.ts`│
│ │ `api/openapi.$.ts` · `api/uploads/$userId.$.ts`│
│ │ `mcp/index.ts` · `[.]well-known/*` · `schema.json.ts`│
└──────────┬───────────────┴──────────────────────────────┬─────────────────┘
│ in-process router client │ HTTP handler
▼ ▼
┌────────────────────────────────────────────────────────────────────────────┐
│ oRPC routers │
│ `packages/api/src/routers/{ai,auth,flags,resume,statistics,storage}.ts` │
│ Procedures: `publicProcedure` / `protectedProcedure` │
│ Middleware: `packages/api/src/middleware/rate-limit/index.ts` │
└──────────┬─────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────┐
│ Services & helpers │
│ `packages/api/src/services/{resume,ai,auth,storage,flags,statistics}.ts` │
│ `packages/api/src/helpers/{resume-access,resume-access-policy}.ts` │
│ `packages/api/src/services/resume-events.ts` (Postgres LISTEN/NOTIFY) │
│ `packages/auth/src/config.ts` (Better Auth) │
└──────────┬──────────────────────────────────────┬──────────────────────────┘
│ Drizzle ORM │ S3 / local FS
▼ ▼
┌──────────────────────────────────┐ ┌──────────────────────────────────┐
│ PostgreSQL (via `pg`) │ │ Storage (S3 or `<workspace>/data`)│
│ `packages/db/src/client.ts` │ │ `packages/api/src/services/storage.ts`│
│ schema: `packages/db/src/schema/*`│ │ served by `routes/uploads/$userId.$.tsx`│
│ migrations: `migrations/` │ └──────────────────────────────────┘
└──────────────────────────────────┘
Component Responsibilities
| Component | Responsibility | File |
|---|---|---|
| Web app shell | TanStack Start app, route tree, SSR/CSR boundary, PWA, builder UI | apps/web/src/router.tsx, apps/web/src/routes/__root.tsx |
| Server entry | Nitro fetch handler wrapping react-start/server-entry |
apps/web/src/server.ts |
| Migration plugin | Walks up to repo root, runs Drizzle migrations on boot | apps/web/plugins/1.migrate.ts |
| Storage plugin | Validates <workspace>/data writability when S3 is unused |
apps/web/plugins/2.storage.ts |
| oRPC router root | Aggregates all sub-routers exposed at /api/rpc and OpenAPI |
packages/api/src/routers/index.ts |
| oRPC context | Header-based auth resolution, publicProcedure/protectedProcedure |
packages/api/src/context.ts |
| Resume service | CRUD, patch (RFC 6902), password, lock, statistics, analysis, events | packages/api/src/services/resume.ts |
| Storage service | S3 + local FS abstraction, image processing via sharp |
packages/api/src/services/storage.ts |
| Resume access policy | Owner/viewer/redaction rules for getBySlug and statistics |
packages/api/src/helpers/resume-access-policy.ts |
| Resume events | Postgres LISTEN/NOTIFY channel for live updates |
packages/api/src/services/resume-events.ts |
| Auth | Better Auth config, OAuth provider, passkey, 2FA, API keys, JWKS | packages/auth/src/config.ts |
| Database | Drizzle client singleton, schema, generated migrations | packages/db/src/client.ts, packages/db/src/schema/*.ts, migrations/ |
| Schema | Zod resume/page/template models shared across web + API + PDF + MCP | packages/schema/src/resume/data.ts, packages/schema/src/templates.ts |
| PDF rendering | React PDF Document, font registration, 14 template implementations |
packages/pdf/src/document.tsx, packages/pdf/src/templates/index.ts, packages/pdf/src/hooks/use-register-fonts.ts |
| Shared UI | Base UI / shadcn-style component library and hooks | packages/ui/src/components/*.tsx, packages/ui/src/hooks/*.tsx |
| MCP server | Model Context Protocol server backed by oRPC routers | apps/web/src/routes/mcp/index.ts, apps/web/src/routes/mcp/-helpers/* |
Pattern Overview
Overall: Modular monolith. One deployable web app + a constellation of source-only TypeScript packages communicating through typed package.json exports. Browser ↔ server communication uses oRPC (typed RPC with REST/OpenAPI generation) instead of REST/tRPC. SSR and client share the same router/queryClient via TanStack Start.
Key Characteristics:
- Source-consumed workspace packages (no per-package
distbuild) keep types end-to-end. - The oRPC router is mounted twice: as a Fetch handler at
/api/rpcand as an in-processcreateRouterClientfor SSR/server functions, with identical types on both paths. - Browser-only code (React PDF preview, PDF.js, canvas) is isolated behind explicit
.browser.tsxfiles andssr: false/ssr: "data-only"route opts to keep SSR bundles small and safe. - Postgres is both the data store and the event bus (
pg_notifychannelresume_updatedfor live builder sync). - Shared concerns (auth, theme, locale, feature flags, oRPC client, query client) are loaded once and passed through TanStack Router
contextrather than fetched per-route.
Layers
Routes (apps/web/src/routes/):
- Purpose: File-based TanStack Router routes; some are pure UI, others embed
server.handlersblocks that act as HTTP endpoints. - Location:
apps/web/src/routes/ - Contains: Page components, route loaders, server-only API handlers, layout wrappers.
- Depends on:
packages/api/routers(mounted at/api/rpc),packages/auth/config(mounted at/api/auth),packages/db/client,packages/api/services/*. - Used by: TanStack Router (route tree is regenerated into
apps/web/src/routeTree.gen.ts).
oRPC API layer (packages/api/src/routers/):
- Purpose: Public typed contract for the browser, in-process callers, and OpenAPI/MCP consumers.
- Location:
packages/api/src/routers/{ai,auth,flags,resume,statistics,storage}.ts, aggregated inpackages/api/src/routers/index.ts. - Contains: Procedure definitions, Zod input/output schemas, REST metadata, rate-limit middleware bindings.
- Depends on:
packages/api/src/context.ts,packages/api/src/dto/*,packages/api/src/services/*. - Used by:
apps/web/src/routes/api/rpc.$.ts(RPCHandler),apps/web/src/routes/api/openapi.$.ts(OpenAPIHandler),apps/web/src/libs/orpc/client.ts(isomorphic client), MCP tools inapps/web/src/routes/mcp/-helpers/tools.ts.
Services / business logic (packages/api/src/services/):
- Purpose: All cross-cutting business rules — resume CRUD, statistics, AI orchestration, storage, feature flags, auth helpers, event publishing.
- Location:
packages/api/src/services/*.ts - Contains: Side-effecting functions with explicit
userId-style inputs. No request/response coupling. - Depends on:
packages/db/client,packages/db/schema,packages/auth/config,packages/schema/resume/*,packages/utils/*,packages/email/transport, AI SDKs. - Used by: Routers, MCP helpers, the
/api/healthroute.
Persistence (packages/db/):
- Purpose: Drizzle ORM client + schema definitions; the only place SQL is written.
- Location:
packages/db/src/client.ts,packages/db/src/schema/{auth,resume,index}.ts,packages/db/src/relations.ts. - Contains: Postgres
Poolsingleton (stored onglobalThis.__poolto survive HMR),drizzle()client, table definitions, relations. - Depends on:
pg,drizzle-orm,packages/env/server. - Migrations: Generated by
drizzle-kitinto the repo-rootmigrations/directory (seepackages/db/drizzle.config.ts) and applied at startup byapps/web/plugins/1.migrate.ts.
Auth (packages/auth/):
- Purpose: Better Auth instance with email/password, OAuth (Google/GitHub/LinkedIn + generic), passkey, 2FA, API keys, JWKS, and a JWT-issuing OAuth provider for MCP clients.
- Location:
packages/auth/src/config.ts,packages/auth/src/functions.ts,packages/auth/src/types.ts. - Mounted at:
apps/web/src/routes/api/auth.$.ts(delegatesrequest → auth.handler(request)after sanitizing OAuth params). - Verifies tokens for MCP at
apps/web/src/routes/mcp/index.tsviaverifyOAuthToken.
PDF rendering (packages/pdf/):
- Purpose: React PDF document and 14 visual templates (named after Pokémon).
- Location:
packages/pdf/src/document.tsxmountsgetTemplatePage(template); templates live underpackages/pdf/src/templates/<name>/. - Shared template primitives:
packages/pdf/src/templates/shared/{filtering,rich-text,sections,primitives,picture,page-size,columns}.ts(x). - Fonts:
packages/pdf/src/hooks/use-register-fonts.tsowns React PDF font registration, standard PDF font handling, CJK fallback stacks, and global hyphenation. - Consumed by: Web builder preview (
apps/web/src/components/resume/preview*.tsx,pdf-canvas.tsx), public resume page (apps/web/src/routes/$username/$slug.tsx), and the OpenAPI/resumes/{id}/downloadprocedure (apps/web/src/routes/api/-helpers/resume-pdf.ts).
Shared schemas (packages/schema/):
- Purpose: Zod source of truth for resume data, templates, page settings, and AI analysis.
- Location:
packages/schema/src/resume/{data,default,sample,analysis}.ts,packages/schema/src/templates.ts,packages/schema/src/page.ts,packages/schema/src/icons.ts. - Used by: API DTOs (
packages/api/src/dto/resume.ts), DB column typing (packages/db/src/schema/resume.ts), import package, PDF rendering, web forms, MCP tool descriptions, and the public JSON schema at/schema.json.
Shared UI (packages/ui/):
- Purpose: Headless/styled component library (Base UI + shadcn-style) used by
apps/web. - Location:
packages/ui/src/components/*.tsx,packages/ui/src/hooks/*.tsx. - Examples:
dialog,dropdown-menu,command,resizable,sonner,tooltip,form,direction. - Styles:
packages/ui/src/styles/globals.css(Tailwind v4 entry).
Support packages:
packages/utils— small focused helpers (color,date,field,file,html,level,locale,network-icons,rate-limit,sanitize,string,style,url, plus Node-onlymonorepo.node,url-security.node, andresume/{docx,patch}).packages/env—@t3-oss/env-coreserver schema;dotenvloads the repo-root.env(packages/env/src/server.ts).packages/email—nodemailertransport +react-emailtemplates (packages/email/src/transport.ts,packages/email/src/templates/*.tsx).packages/import— converters for JSON Resume, Reactive Resume v3/v4 JSON (packages/import/src/*.tsx).packages/ai— Zustand store, AI prompts (packages/ai/src/prompts/*.md), patch-resume tool, sanitize/extraction helpers consumed by the AI router.packages/fonts— generated Google Fonts metadata (packages/fonts/src/webfontlist.json,packages/fonts/src/index.ts).packages/scripts— repo-level scripts (packages/scripts/database/reset.ts,packages/scripts/fonts/generate.ts).packages/config— shared TypeScript/Vitest base configs (packages/config/tsconfig.base.json,packages/config/vitest.config.ts).packages/runtime-externals— declaresbcrypt,sharp,@aws-sdk/client-s3so they remain runtime-only (externalized inapps/web/vite.config.ts).
Data Flow
Primary Request Path (browser RPC call)
- Browser route uses
orpc.resume.getById.queryOptions(...)fromapps/web/src/libs/orpc/client.ts:84to fetch data. - The isomorphic oRPC client (
apps/web/src/libs/orpc/client.ts:28-47) creates anRPCLinkpointing at${window.location.origin}/api/rpcwithcredentials: "include"and aBatchLinkPlugin. - Request hits the file route
apps/web/src/routes/api/rpc.$.ts, whereRPCHandler(line 9) dispatches withBatchHandlerPlugin,RequestHeadersPlugin, andStrictGetMethodPlugin. publicProcedure(packages/api/src/context.ts:79) resolves the user from headers (x-api-key→ bearer JWT via JWKS → Better Auth session cookie).protectedProcedure(packages/api/src/context.ts:90) rejects unauthenticated callers withORPCError("UNAUTHORIZED").- Router handler in
packages/api/src/routers/resume.tscalls intopackages/api/src/services/resume.ts, which queries Drizzle (packages/db/src/client.ts:32). - Response is serialized back through oRPC and consumed by TanStack Query / the route component.
SSR / Server-Side Path
- During SSR,
apps/web/src/libs/orpc/client.ts:13-27short-circuits the HTTP path viacreateRouterClient(router, { context: async () => ({ locale, reqHeaders }) }). - The same
publicProcedure/protectedProceduremiddleware runs in-process — no socket hop — but still resolves auth from the original request headers viagetRequestHeaders()from@tanstack/react-start/server. - Route loaders (e.g.
apps/web/src/routes/builder/$resumeId/route.tsx:39-44) populate the query cache viacontext.queryClient.ensureQueryData(orpc.resume.getById.queryOptions(...)).
Resume Live-Update Flow
subscribeprocedure inpackages/api/src/routers/resume.ts:76returns an async generator.- On any mutation,
packages/api/src/services/resume-events.ts:37callspg_notify('resume_updated', JSON.stringify(event)). - Subscribing clients receive
resume.updatedSSE-style events via oRPC streaming (apps/web/src/libs/orpc/client.ts:51-82,streamClient). - The builder route consumes them through
useResumeUpdateSubscription(apps/web/src/components/resume/builder-resume-draft.ts).
PDF Download Path
- Web client requests
GET /api/openapi/resumes/{id}/download(oRPC OpenAPI handler atapps/web/src/routes/api/openapi.$.ts). downloadResumePdfProcedureinapps/web/src/routes/api/-helpers/resume-pdf.tsloads the resume, rendersResumeDocumentfrompackages/pdf/src/document.tsx, persists to storage viagetStorageService(), and returns/streams the PDF.
Public Resume Path
apps/web/src/routes/$username/$slug.tsxusesssr: "data-only"— server fetchesresume.getBySlugbut renders the React PDF preview only on the client.packages/api/src/helpers/resume-access-policy.tsenforces visibility, redacts non-public fields, and throwsNEED_PASSWORDfor password-protected resumes (the route then redirects to/auth/resume-password).
State Management:
- Server cache: TanStack Query (
apps/web/src/libs/query/client.ts) wrapped by oRPC'screateTanstackQueryUtils. - Local UI state: Zustand stores under
apps/web/src/routes/builder/$resumeId/-store/{section,sidebar}.ts,apps/web/src/dialogs/store.ts,apps/web/src/components/command-palette/store.ts,apps/web/src/components/resume/builder-resume-draft.ts. - Router context:
theme,locale,session,flags,queryClient,orpcare computed once inapps/web/src/router.tsx(and again in the root routebeforeLoad) and reused by descendants. - Cookies: builder layout (
BUILDER_LAYOUT_COOKIE_NAME), theme, and locale are persisted viagetCookie/setCookieserver functions.
Key Abstractions
oRPC procedures (os.$context<ORPCContext>()):
- Purpose: Typed RPC procedures that double as REST endpoints and MCP tool surfaces.
- Examples:
publicProcedureandprotectedProcedureinpackages/api/src/context.ts:79/:90. - Pattern:
procedure.route({...openapi metadata}).input(zodSchema).use(rateLimitMiddleware).output(zodSchema).handler(async ({ context, input }) => ...).
Drizzle tables:
- Purpose: Strongly-typed Postgres schema;
.jsonb()columns are typed against Zod-derived TypeScript (ResumeData,StoredResumeAnalysis). - Examples:
packages/db/src/schema/resume.ts(resume, resumeStatistics, resumeAnalysis),packages/db/src/schema/auth.ts(Better Auth tables). - Pattern:
pg.pgTable("name", { ... }, (t) => [pg.index().on(...), pg.unique().on(...)]).
Template pages (React PDF):
- Purpose: One
TemplatePagecomponent per visual template, mapped by name inpackages/pdf/src/templates/index.ts. - Examples:
packages/pdf/src/templates/azurill/AzurillPage.tsx,packages/pdf/src/templates/onyx/OnyxPage.tsx. - Pattern:
(props: { page: LayoutPage; pageIndex: number }) => JSX, consumingRenderProviderfrompackages/pdf/src/context.tsx.
Base UI components:
- Purpose: Headless primitives styled with Tailwind v4 and exported as composable parts.
- Examples:
packages/ui/src/components/dialog.tsx,packages/ui/src/components/command.tsx,packages/ui/src/components/resizable.tsx. - Imported via deep paths:
import { Dialog } from "@reactive-resume/ui/components/dialog";.
TanStack Router file routes:
- Purpose: Page components, loaders, and optional
server.handlersblocks per file. - Examples:
apps/web/src/routes/builder/$resumeId/route.tsx,apps/web/src/routes/api/rpc.$.ts. - Pattern:
export const Route = createFileRoute("/path")({ component, loader, beforeLoad, server: { handlers: { GET, POST } }, ssr });.
Entry Points
Vite + Nitro build entry:
- Location:
apps/web/vite.config.ts - Triggers:
pnpm dev,pnpm build. Wires TanStack Start, Tailwind v4, Lingui (i18n), Nitro plugins, and the Vite PWA plugin. - Externals:
bcrypt,sharp,@aws-sdk/client-s3(declared inpackages/runtime-externals).
Server fetch entry:
- Location:
apps/web/src/server.ts - Responsibilities: Wraps
@tanstack/react-start/server-entryand substitutessrvx'sFastResponseas the globalResponse.
Nitro startup plugins:
apps/web/plugins/1.migrate.ts— resolves the repo-rootmigrations/folder, opens its ownpg.Pool, runs Drizzle migrations on boot, then closes the pool.apps/web/plugins/2.storage.ts— when S3 env vars are absent, ensures the local storage directory is writable before serving requests.
Router entry:
- Location:
apps/web/src/router.tsx - Responsibilities: Builds
queryClient, loadstheme/locale/session/flagsin parallel, creates the TanStack Router with router context, registers SSR query integration.
Root route:
- Location:
apps/web/src/routes/__root.tsx - Responsibilities: HTML shell, providers (
I18nProvider,ThemeProvider,HotkeysProvider,DirectionProvider,TooltipProvider,ConfirmDialogProvider,PromptDialogProvider), PWA head/scripts,DialogManager,CommandPalette,Toaster.
HTTP endpoints (route server.handlers):
apps/web/src/routes/api/rpc.$.ts—/api/rpc/*oRPC handler (browser RPC).apps/web/src/routes/api/auth.$.ts—/api/auth/*Better Auth handler (with OAuth payload sanitization).apps/web/src/routes/api/health.ts—/api/healthJSON probe (db + storage with timeouts).apps/web/src/routes/api/openapi.$.ts—/api/openapi/*OpenAPI handler + spec.apps/web/src/routes/api/uploads/$userId.$.tsandapps/web/src/routes/uploads/$userId.$.tsx— signed/etagged static file serving from storage.apps/web/src/routes/mcp/index.ts—/mcpModel Context Protocol server (OAuth-protected).apps/web/src/routes/[.]well-known/*—/.well-known/oauth-authorization-server,/.well-known/oauth-protected-resource,/.well-known/openid-configuration,/.well-known/mcpdiscovery documents.apps/web/src/routes/schema[.]json.ts—/schema.jsonpublic JSON Schema forResumeData.
Builder + public resume entry points:
apps/web/src/routes/builder/$resumeId/route.tsx— authenticated builder shell (header + resizable left/right sidebars + artboard outlet + assistant).apps/web/src/routes/builder/$resumeId/index.tsx—ssr: false; lazy-loadedPreviewPagerunning React PDF/canvas only in the browser.apps/web/src/routes/$username/$slug.tsx—ssr: "data-only"; public/shared resume view (with password gating viaNEED_PASSWORDORPCError).
Architectural Constraints
- Single Node process: Everything (web, oRPC, auth, MCP, OpenAPI, file serving) runs in one Node 24 process on port 3000. No separate API service.
- Source-only workspace packages:
package.jsonexportspoint atsrc/*.ts(x); do not assumedistoutput exists. The Vite build externalizesbcrypt,sharp,@aws-sdk/client-s3(apps/web/vite.config.ts:55). - Globals on
globalThisfor DB pool:packages/db/src/client.ts:8-11caches thepg.Pooland Drizzle client onglobalThis.__pool/globalThis.__drizzleto survive HMR reloads. - Browser-only PDF rendering: PDF.js,
@react-pdf/renderercanvas, and the builder preview must stay off the SSR path. Use.browser.tsxsuffix files andssr: false(builder preview) orssr: "data-only"(public resume). - Generated route tree:
apps/web/src/routeTree.gen.tsis regenerated by TanStack Router tooling; never edit by hand. - Migrations folder location:
drizzle-kitwrites to../../migrationsfrompackages/db/drizzle.config.ts, so all migration directories live at the repo root, not inside the package. - DATABASE_URL not auto-loaded for drizzle-kit:
pnpm db:migrate/pnpm db:generaterequireDATABASE_URLexported in the shell; only the runtime Node code loads.envviapackages/env/src/server.ts. - Rate limiting is production-only:
packages/api/src/middleware/rate-limit/index.ts:5and the Better Auth config gate rate limits onprocess.env.NODE_ENV === "production". - OAuth audience binding:
verifyOAuthToken(packages/auth/src/config.ts:40) only accepts JWTs whoseaudmatches${APP_URL}(with/without trailing slash) or${APP_URL}/mcp. - Postgres LISTEN/NOTIFY coupling: Live resume updates depend on a single shared
pg.Pool(packages/db/src/client.ts:13); scaling beyond one process requires replacingpg_notifywith a broker.
Anti-Patterns
Ad-hoc fetching of router context
What happens: Components separately calling getSession(), getTheme(), or getLocale() instead of reading them from TanStack Router context.
Why it's wrong: The root route's beforeLoad already loads these in parallel (apps/web/src/routes/__root.tsx:82-93) and exposes them via Route.useRouteContext(); duplicating the calls causes extra round-trips during SSR and triggers locale reloads.
Do this instead: Use Route.useRouteContext() or read from a parent route loader, as apps/web/src/routes/__root.tsx:101 does for theme/locale.
Direct DB or storage imports in client code
What happens: Importing @reactive-resume/db/client or @reactive-resume/api/services/storage from a non-route component.
Why it's wrong: Pulls pg, sharp, bcrypt, @aws-sdk/client-s3 into the client bundle (which Vite externalizes — the build will fail or break at runtime).
Do this instead: Call the corresponding oRPC procedure from packages/api/src/routers/*. Server-only imports belong inside route server.handlers blocks or .server.tsx files like apps/web/src/libs/resume/pdf-document.server.tsx.
PDF/canvas code in shared modules
What happens: Importing @react-pdf/renderer or PDF.js from a file that participates in SSR.
Why it's wrong: These libraries crash under Node SSR (canvas/DOM dependencies).
Do this instead: Keep browser code in *.browser.tsx / pdf-canvas.tsx and gate with ssr: false (e.g. apps/web/src/routes/builder/$resumeId/index.tsx:6) or ssr: "data-only" (e.g. apps/web/src/routes/$username/$slug.tsx:40).
Bypassing the resume access policy
What happens: Reading resume rows directly from Drizzle in a public procedure without applying redaction.
Why it's wrong: Leaks owner-only fields (password hash, private flags, statistics) and breaks the password-gate flow that depends on NEED_PASSWORD errors.
Do this instead: Route through packages/api/src/helpers/resume-access-policy.ts (assertCanView, redactResumeForViewer, shouldCountForStatistics) as packages/api/src/services/resume.ts does.
Hand-editing generated files
What happens: Modifying apps/web/src/routeTree.gen.ts or migration SQL after Drizzle writes it.
Why it's wrong: Edits are overwritten on the next tanstack-router regen or migration; data drift between snapshots and SQL breaks future migrations.
Do this instead: Add a route file under apps/web/src/routes/, or change the Drizzle schema in packages/db/src/schema/*.ts and run pnpm db:generate.
Error Handling
Strategy: Typed errors via ORPCError codes; HTTP responses for low-level handlers; route-level defaultErrorComponent and onError for UI fallbacks.
Patterns:
- Procedures throw
ORPCError("UNAUTHORIZED"|"NOT_FOUND"|"NEED_PASSWORD"|...)or use.errors({ ... })to declare typed application errors (e.g.RESUME_SLUG_ALREADY_EXISTS,RESUME_VERSION_CONFLICTinpackages/api/src/routers/resume.ts). - The OAuth bearer / API key / session resolvers in
packages/api/src/context.ts:14-55swallow verification errors and log viaconsole.warnso unauthenticated requests fall through to the next strategy. apps/web/src/routes/api/rpc.$.tsandapps/web/src/routes/api/openapi.$.tsinstallonErrorinterceptors that log every server error with a tag ([oRPC Server],[OpenAPI]).- Route-level
onError(e.g.apps/web/src/routes/$username/$slug.tsx:24) translatesNEED_PASSWORDinto a redirect. - Top-level UI fallbacks:
apps/web/src/components/layout/error-screen.tsx,loading-screen.tsx,not-found-screen.tsx(wired inapps/web/src/router.tsx:30-32). - Healthcheck uses a 1.5s timeout helper (
apps/web/src/routes/api/health.ts:22-34) so a stuck dependency cannot stall the probe.
Cross-Cutting Concerns
Logging: console.info/console.warn/console.error with bracketed prefixes (e.g. [oRPC Server], [Healthcheck], [oRPC client]). No external log shipping is wired.
Validation: Zod 4 everywhere — Drizzle column types (packages/db/src/schema/resume.ts), oRPC input/output schemas, AI tool inputs, environment variables (packages/env/src/server.ts), and the public /schema.json route.
Authentication: Better Auth in packages/auth/src/config.ts (Drizzle adapter, email/password + OAuth + passkey + 2FA + admin + API keys + JWT + generic OAuth + dynamic client registration + custom oauthProvider for MCP). The unified resolver in packages/api/src/context.ts:64 is the only place that decides which credential wins.
Internationalization: Lingui — apps/web/lingui.config.ts, apps/web/locales/*.po, apps/web/src/libs/locale.ts, RTL toggling in __root.tsx.
Theming: apps/web/src/libs/theme.ts (cookie-backed), apps/web/src/components/theme/provider.tsx (next-themes).
Rate limiting: @orpc/experimental-ratelimit with an in-memory ratelimiter from packages/api/src/middleware/rate-limit/index.ts. Trusted IP headers come from packages/utils/src/rate-limit.ts.
Feature flags: Server-resolved at boot (packages/api/src/routers/flags.ts and packages/api/src/services/flags.ts), then carried in router context via apps/web/src/router.tsx:20.
Architecture analysis: 2026-05-11