From 7700ba4520716f833e1ef3b52e877507a8048b96 Mon Sep 17 00:00:00 2001 From: imbytecat Date: Thu, 2 Apr 2026 00:37:44 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=BF=AE=E6=AD=A3=20AGENTS.md=20?= =?UTF-8?q?=E4=B8=8E=E4=BB=A3=E7=A0=81=E5=BA=93=E7=9A=84=2012=20=E5=A4=84?= =?UTF-8?q?=E4=B8=8D=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 103 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 33 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index df6f207..c586de3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# AGENTS.md - AI Coding Agent Guidelines +# AGENTS.md — AI Coding Agent Guidelines ## Project Overview @@ -16,8 +16,8 @@ bun run dev # Vite dev server (localhost:3000) bun run build # Production build → .output/ bun run compile # Compile to standalone binary (current platform) -bun run fix # Biome auto-fix (lint + format) -bun run typecheck # TypeScript check +bun run fix # Biome auto-fix (lint + format, runs `biome check --write`) +bun run typecheck # TypeScript check (`tsc --noEmit`) bun run db:push # Push schema to dev DB (dev only, fast iteration) bun run db:generate # Generate migration SQL files (for production) bun run db:migrate # Apply migrations (production deployment) @@ -27,36 +27,69 @@ bun test path/to/test.ts # Run single test (not yet configured) ## Code Style -**Formatting (Biome):** 2-space indent, LF, single quotes, no semicolons, always arrow parens `(x) => x` +**Formatting (Biome):** 2-space indent, LF, single quotes, semicolons as needed (omitted unless syntactically required), max line width **120**, arrow parens always `(x) => x` -**Imports** — Biome auto-organizes: 1) External → 2) `@/*` aliases → 3) `import type` +**Imports** — Biome auto-organizes alphabetically within two groups. `import type` is interleaved in its group alphabetically, NOT a separate group: ```typescript +// 1) External packages (alphabetical, types interleaved) +import { useSuspenseQuery } from '@tanstack/react-query' import { createFileRoute } from '@tanstack/react-router' -import { db } from '@/server/db' import type { ReactNode } from 'react' +// 2) @/* aliases (alphabetical, types interleaved) +import { orpc } from '@/client/orpc' +import { TodoForm } from '@/components/TodoForm' +import type { RouterClient } from '@/server/api/types' ``` -**TypeScript:** `strict: true`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`. Use `@/*` path aliases (→ `src/*`). +**TypeScript:** `strict: true`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`, `erasableSyntaxOnly`. Use `@/*` path aliases (→ `src/*`). For root-level files outside `src/`, use `@/../file` (e.g., `@/../package.json`). **Naming:** Files (utils): `kebab-case.ts` | Files (components): `PascalCase.tsx` | Components: `const Button = () => {}` | Functions: `camelCase` | Constants: `UPPER_SNAKE` | Types: `PascalCase` -**React:** Arrow function components (Biome enforced). Routes: `export const Route = createFileRoute(...)`. Data: `useSuspenseQuery(orpc.feature.list.queryOptions())` +**React:** Arrow function components (Biome enforced via `useArrowFunction: "error"`). Routes: `export const Route = createFileRoute(...)`. Data: `useSuspenseQuery(orpc.feature.list.queryOptions())` -**Errors:** `try-catch` for async; `ORPCError` with codes (`NOT_FOUND`, `INPUT_VALIDATION_FAILED`). Never empty catch blocks. +**Errors:** `ORPCError` with codes (`NOT_FOUND`, `INTERNAL_SERVER_ERROR`, `INPUT_VALIDATION_FAILED`). Never empty catch blocks. Validation errors are auto-transformed by interceptors (see ORPC section). ## ORPC Pattern -**1. Contract** (`src/server/api/contracts/feature.contract.ts`) — Zod schemas for input/output -**2. Router** (`src/server/api/routers/feature.router.ts`) — Handler implementations using `os.feature.list.use(db).handler(...)` -**3. Register** — Export from `contracts/index.ts` and `routers/index.ts` -**4. Client** — `useSuspenseQuery(orpc.feature.list.queryOptions())` / `useMutation(orpc.feature.create.mutationOptions())` +**Contract → Router → Handler → Client** (fully type-safe, zero manual type definitions): + +**1. Contract** (`src/server/api/contracts/feature.contract.ts`) — Zod schemas **generated from Drizzle tables**: +```typescript +const selectSchema = createSelectSchema(featureTable) +const insertSchema = createInsertSchema(featureTable).omit(generatedFieldKeys) +export const list = oc.input(z.void()).output(z.array(selectSchema)) +export const create = oc.input(insertSchema).output(selectSchema) +``` + +**2. Router** (`src/server/api/routers/feature.router.ts`) — Handlers chaining `db` middleware: +```typescript +export const list = os.feature.list.use(db).handler(async ({ context }) => { + return context.db.query.featureTable.findMany({ orderBy: { createdAt: 'desc' } }) +}) +``` + +**3. Register** — Add to `contracts/index.ts` and `routers/index.ts` (barrel exports) + +**4. Client** — `useSuspenseQuery(orpc.feature.list.queryOptions())` / `useMutation(orpc.feature.create.mutationOptions())`. Mutation invalidation is configured globally via `experimental_defaults` in `src/client/orpc.ts`. + +**5. Interceptors** — Registered at **Handler level** (`RPCHandler`/`OpenAPIHandler` in route files), NOT in `server.ts`: +```typescript +const handler = new RPCHandler(router, { + interceptors: [onError(logError)], + clientInterceptors: [onError(handleValidationError)], +}) +``` + +**6. SSR** — `createIsomorphicFn()` in `src/client/orpc.ts` switches between `createRouterClient` (server) and `RPCLink` (client). Routes use `ensureQueryData` in loaders for SSR prefetch. + +**7. OpenAPI** — `OpenAPIHandler` + `OpenAPIReferencePlugin` with Scalar docs at `/api/docs`, spec at `/api/spec.json`. ## Database (Drizzle ORM v1 beta) - **Driver**: `drizzle-orm/postgres-js` (NOT `bun-sql`) | **Validation**: `drizzle-orm/zod` (NOT `drizzle-zod`) - **Relations**: `defineRelations()` in `src/server/db/relations.ts` (RQBv2 — `drizzle()` only needs `{ relations }`) - **Query style**: RQBv2 object syntax — `orderBy: { createdAt: 'desc' }`, `where: { id: 1 }` -- **Schema**: All tables must include `id` (UUIDv7), `createdAt`, `updatedAt` (see `src/server/db/fields.ts`) +- **Schema**: All tables spread `...generatedFields` for `id` (UUIDv7), `createdAt`, `updatedAt` (see `src/server/db/fields.ts`) ### Database Workflow - **Local dev**: `db:push` — fast iteration, no migration files needed @@ -81,7 +114,7 @@ import type { ReactNode } from 'react' ## Critical Rules -**DO:** `bun run fix` before committing | `@/*` path aliases | `createdAt`/`updatedAt` on all tables | `ORPCError` with proper codes | `drizzle-orm/zod` for validation | RQBv2 object syntax | Update docs with code changes +**DO:** `bun run fix` before committing | `@/*` path aliases | `...generatedFields` on all tables | `ORPCError` with proper codes | `drizzle-orm/zod` for validation | RQBv2 object syntax | Update docs with code changes **DON'T:** `npm`/`npx`/`node`/`yarn`/`pnpm` | Edit `routeTree.gen.ts` | `as any`/`@ts-ignore`/`@ts-expect-error` | Commit `.env` | Empty catch blocks | Import from `drizzle-zod` | RQBv1 callback-style API | `drizzle-orm/bun-sql` driver | Pass `schema` to `drizzle()` | Import `os` from `@orpc/server` in middleware (use `@/server/api/server`) | Leave docs out of sync @@ -93,27 +126,31 @@ import type { ReactNode } from 'react' ``` src/ -├── client/orpc.ts # ORPC client + TanStack Query utils -├── components/ # React components +├── client/orpc.ts # ORPC client (isomorphic SSR/CSR) + TanStack Query utils +├── components/ # React components (PascalCase.tsx, flat structure) ├── routes/ # TanStack Router file routes -│ ├── __root.tsx # Root layout -│ └── api/ # API routes (OpenAPI, health, RPC) +│ ├── __root.tsx # Root layout (shell, meta, error/notFound, DevTools) +│ ├── index.tsx # Home route (loader + component) +│ └── api/ # API routes +│ ├── $.ts # OpenAPI handler (Scalar docs + spec) +│ ├── rpc.$.ts # RPC handler (interceptors registered here) +│ └── health.ts # Health check endpoint ├── server/ │ ├── api/ -│ │ ├── contracts/ # ORPC input/output schemas (Zod) -│ │ ├── routers/ # ORPC handler implementations -│ │ ├── middlewares/ # Middleware (db, auth) -│ │ ├── interceptors.ts # Shared error interceptors -│ │ ├── context.ts # Request context -│ │ ├── server.ts # ORPC server instance -│ │ └── types.ts # Type exports +│ │ ├── contracts/ # ORPC contracts (Zod schemas from Drizzle tables) +│ │ ├── routers/ # ORPC handlers (use db middleware, throw ORPCError) +│ │ ├── middlewares/ # Middleware (db context injection) +│ │ ├── interceptors.ts # Error interceptors (registered in route handlers) +│ │ ├── context.ts # Request context types (BaseContext, DBContext) +│ │ ├── server.ts # implement(contract).$context() +│ │ └── types.ts # RouterClient, RouterInputs, RouterOutputs │ └── db/ -│ ├── schema/ # Drizzle table definitions -│ ├── fields.ts # Shared field builders -│ ├── relations.ts # Drizzle relations (RQBv2) -│ └── index.ts # Database instance -├── env.ts # Environment variable validation -├── router.tsx # Router configuration +│ ├── schema/ # Drizzle table definitions (...generatedFields spread) +│ ├── fields.ts # Shared fields: pk (UUIDv7), createdAt, updatedAt +│ ├── relations.ts # Drizzle relations (RQBv2 defineRelations) +│ └── index.ts # DB factory (createDB, getDB singleton) +├── env.ts # Environment variable validation (@t3-oss/env-core) +├── router.tsx # Router + QueryClient + SSR query integration ├── routeTree.gen.ts # Auto-generated (DO NOT EDIT) -└── styles.css # Tailwind entry +└── styles.css # Tailwind entry (@import "tailwindcss") ```