From 5dd54ec9e9232b7e54d34a7b13e8a36e06fdcbff Mon Sep 17 00:00:00 2001 From: imbytecat Date: Fri, 24 Apr 2026 20:38:51 +0800 Subject: [PATCH] =?UTF-8?q?docs(agents):=20=E5=90=8C=E6=AD=A5=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E7=AE=80=E5=8C=96=E5=90=8E=E7=9A=84=E8=A7=84=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DB:module-level const db(删掉 getDB/closeDB 的描述),说明为什么 不需要兼容 Cloudflare Workers 的 lazy init - Routers 直接 import db,不再过 middleware;只在真正需要 per-request 上下文时才新建 middlewares/ - Layout 刷新 db/ 与 api/ 目录的注释;Don'ts 补上不要回退到 lazy DB 单例 --- AGENTS.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c3cc641..6fed0f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,7 +43,8 @@ Before committing: `bun run fix && bun run typecheck`. No CI, no pre-commit hook ``` Do NOT use the v2 object form (`orderBy: { createdAt: 'desc' }`, `where: { id }`) — it won't type-check. - To add relations later: declare per-table with `relations()` from `drizzle-orm` and export them from the same file as the table; they get picked up automatically because `index.ts` does `drizzle({ schema })` via `import *`. -- Every table must spread `...generatedFields` from `src/server/db/fields.ts` (gives `id` UUIDv7 with `$defaultFn` fallback, `createdAt`, `updatedAt` with `$onUpdateFn`). There's also a `generatedFieldKeys` helper to feed `createInsertSchema(...).omit(...)`. +- Every table must spread `...generatedFields` from `src/server/db/fields.ts` (`id` UUIDv7 via `$defaultFn(uuidv7)`, `createdAt`, `updatedAt` with `$onUpdateFn`). `generatedFieldKeys` (hand-written `as const`) feeds `createInsertSchema(...).omit(...)`. +- `src/server/db/index.ts` exports a module-level `const db = drizzle(...)` — not a lazy singleton. On Bun this is a long-lived process, so top-level side effects are fine and requested. Don't reintroduce `getDB/closeDB` ceremony; the Nitro shutdown plugin calls `db.$client.end()` directly. (Cloudflare Workers would need per-request init — we don't support that deployment target.) - `drizzle.config.ts` runs outside Vite — `@/*` path aliases do NOT resolve there. It currently does `import { env } from './src/env'` (relative). Preserve that. - The `./drizzle/` migrations directory is gitignored-by-absence right now (no migrations yet). Migrations are applied via the CLI (`./server migrate`, see "CLI & single-binary deploy" below), NOT at server startup. Dev uses `db:push`. Don't mix `push` and `migrate` on the same DB. @@ -75,7 +76,7 @@ Contract → Router → Handler → Client, all type-safe from a single contract const insertSchema = createInsertSchema(todoTable).omit(generatedFieldKeys) ``` Barrel-aggregated in `contracts/index.ts` as `export const contract = { todo }`. -- Routers chain the `db` middleware (`src/server/api/middlewares/db.middleware.ts`, injects the `getDB()` singleton into context). Barrel in `routers/index.ts` builds `os.router({ todo })`. +- Routers (`src/server/api/routers/*.router.ts`) import `db` directly from `@/server/db` in their handlers. There is **no `middlewares/` directory** by default — `db` doesn't need one (module-level const). When you actually need per-request context (auth, tenant, rate-limit), create `src/server/api/middlewares/.middleware.ts` with `os.middleware(...)` and extend `BaseContext` in `context.ts`. - **Interceptors are attached at the handler level, not in `server.ts` and not on `os`.** Both `src/routes/api/rpc.$.ts` (`RPCHandler`) and `src/routes/api/$.ts` (`OpenAPIHandler`) register `[onError(logError)]` (server) and `[onError(handleValidationError)]` (client). The validation interceptor rewrites `BAD_REQUEST + ValidationError` into `INPUT_VALIDATION_FAILED` (422) and output validation errors into `OUTPUT_VALIDATION_FAILED`. - OpenAPI/Scalar: docs at `/api/docs`, spec at `/api/spec.json` (handler prefix `/api`, plugin paths `/docs` and `/spec.json`). - **SSR isomorphism** (`src/client/orpc.ts`): `createIsomorphicFn().server(createRouterClient(...)).client(new RPCLink(...))`. Server branch reads `getRequestHeaders()` for context; client branch POSTs to `${origin}/api/rpc`. @@ -122,17 +123,17 @@ src/ ├── server/ │ ├── api/ │ │ ├── server.ts # the ONLY place to build `os` -│ │ ├── context.ts # BaseContext / DBContext types +│ │ ├── context.ts # BaseContext (add per-request fields when you add middlewares) │ │ ├── interceptors.ts # logError, handleValidationError +│ │ ├── types.ts # Router{Client,Inputs,Outputs} derived from Contract │ │ ├── contracts/ # Zod schemas from Drizzle tables (barrel: contract) -│ │ ├── routers/ # os.* handlers (barrel: router) -│ │ └── middlewares/ # db middleware injects getDB() singleton +│ │ └── routers/ # os.* handlers (barrel: router) — import db directly │ ├── db/ -│ │ ├── index.ts # createDB({ connection, schema }) + getDB()/closeDB() singleton -│ │ ├── fields.ts # pk (UUIDv7), createdAt, updatedAt, generatedFields(Keys) +│ │ ├── index.ts # module-level `export const db = drizzle({...})` +│ │ ├── fields.ts # generatedFields (id/createdAt/updatedAt) + generatedFieldKeys │ │ └── schema/ # pgTable definitions; also put `relations()` here when adding │ └── plugins/ -│ └── shutdown.ts # SIGINT/SIGTERM → closeDB() with 500ms delay +│ └── shutdown.ts # SIGINT/SIGTERM → db.$client.end() with 500ms delay ├── env.ts # t3-oss env validation ├── router.tsx # QueryClient + setupRouterSsrQueryIntegration └── routeTree.gen.ts # auto-generated, do not edit @@ -146,6 +147,7 @@ Nitro plugins are wired in `vite.config.ts` (`nitro({ plugins: [...] })`), not v - Don't edit `routeTree.gen.ts`. - Don't eager-import anything from `.output/` in `bin.ts` or any module it statically imports — it starts the HTTP server as a side effect. Subcommands must be lazy via citty's `() => import(...)` thunks. - Don't re-add an auto-migrate Nitro plugin. Migrations are an explicit deploy step via `./server migrate`. +- Don't reintroduce `getDB/closeDB` or any "lazy DB init" pattern — that's a Cloudflare Workers shape; we deploy on Bun processes. - Don't import `os` from `@orpc/server` in middleware/routers — always `@/server/api/server`. - Don't import from `drizzle-orm/zod` (1.0 beta only). Use `drizzle-zod`. - Don't use RQB v2 object syntax, `defineRelations`, or pass `relations` to `drizzle()`. All are 1.0 beta.