Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/syhily/yufan.me/llms.txt

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

yufan.me uses oRPC for all typed browser-to-server communication. Every HTTP call goes through /rpc/* — there are no ad-hoc fetch calls for data mutations. The browser client is built directly from typeof apiRouter, which means TypeScript enforces input and output types end-to-end without a separate code-generation step.

Base procedures and permission tiers

All procedures are built from one of four base procedure builders declared in src/server/http/orpc-base.ts. Each base chains its own auth and role middleware; the leaf procedure picks one and inherits the guard automatically.
BaseGuardUsed for
publicProcNo auth gate; csrfGuard on non-GETAnonymous + CSRF mutations
authedProcrequireAuth + csrfGuardAny logged-in user
authorProcrequireRole('author') + csrfGuardAuthors and admins
adminProcrequireRole('admin') + csrfGuardAdmins only
All four bases share the same root built from os.$context<HandlerContext>(), so the Hono-to-context plumbing in app.ts is type-safe end-to-end. After an auth middleware resolves, context.viewer is narrowed from ViewerContext | null to the non-null ViewerContext type.

Procedure shape

Each controller exports a domain router built from procedures in this shape:
procBase
  .input(z.object({ ... }))   // Zod schema — single flat object, no { body, query, params } buckets
  .output(z.object({ ... }))  // Use z.void() for 204-like procedures
  .handler(({ input, context }) => { ... })
Handlers orchestrate only — all business logic stays in server/domains/<x>/service.ts. Procedures throw ORPCError('CODE', { message }) which the onErrorHandler in server/http/errors.ts maps to HTTP status codes.

apiRouter shape

src/server/http/api-router.ts composes all domain controllers into a single apiRouter object. The admin sub-tree mirrors the /admin URL hierarchy. The browser client is typed as typeof apiRouter — adding a procedure to a controller and wiring it here is the only step needed to expose it to the client.
export const apiRouter = {
  account: accountRouter,        // login, logout, sign-up, password reset
  analytics: analyticsRouter,    // visit ingestion (public)
  comments: commentsRouter,      // comment creation and likes (public, CSRF-guarded)
  image: imageRouter,            // image metadata (public)
  music: musicRouter,            // music track metadata (public)
  admin: {
    users: adminUsersRouter,
    auditLog: auditLogRouter,
    settings: adminSettingsRouter,
    cache: adminCacheRouter,
    mail: adminMailRouter,
    friends: adminFriendsRouter,
    categories: adminCategoriesRouter,
    tags: adminTagsRouter,
    images: adminImagesRouter,
    music: adminMusicRouter,
    pages: adminPagesRouter,
    posts: adminPostsRouter,
    renders: adminRendersRouter,
    comments: adminCommentsRouter,
    backup: adminBackupRouter,
  },
}

Resource routes

Non-JSON output is handled by native Hono routes in server/http/resources/ — these do not go through oRPC:
RouteOutput
/feed.xmlRSS 2.0 feed
/atom.xmlAtom 1.0 feed
/sitemap.xmlXML sitemap
/og/*Open Graph images (PNG)
/calendar/:year/:monthCalendar SVG
/rpc/*oRPC JSON mount point
RBAC on resource routes is enforced via server/http/middlewares/hono-rbac.ts::requireRoleMw.

OpenAPI spec

In development, the full OpenAPI specification is auto-generated from apiRouter and available at:
  • /openapi.json — machine-readable spec
  • /docs — interactive Swagger UI

CSRF protection

All non-GET procedures have a csrfGuard applied at the mount level — handlers never call validateRequestCsrf themselves. The browser oRPC client handles CSRF token injection automatically, so application code does not need to manage tokens directly.

Error handling

Procedures and services throw ORPCError('CODE', { message }). The onErrorHandler in server/http/errors.ts maps oRPC error codes to HTTP status codes. Service layers must not throw HTTPException — only ORPCError or the domain’s own DomainError / ActionFailure types from server/infra/http/errors.
To audit the full permission matrix for the entire API, run one grep against the controllers directory. The base procedure name on each handler tells you the minimum required role:
grep -rn "adminProc\|authorProc\|authedProc\|publicProc" src/server/http/controllers/
Smoke coverage for the permission matrix lives in tests/server.http.orpc-smoke.test.ts.

Build docs developers (and LLMs) love