docs(agents): 同步架构简化后的规约
- 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 单例
This commit is contained in:
@@ -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.
|
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 *`.
|
- 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.
|
- `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.
|
- 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)
|
const insertSchema = createInsertSchema(todoTable).omit(generatedFieldKeys)
|
||||||
```
|
```
|
||||||
Barrel-aggregated in `contracts/index.ts` as `export const contract = { todo }`.
|
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/<name>.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`.
|
- **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`).
|
- 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`.
|
- **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/
|
├── server/
|
||||||
│ ├── api/
|
│ ├── api/
|
||||||
│ │ ├── server.ts # the ONLY place to build `os`
|
│ │ ├── 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
|
│ │ ├── interceptors.ts # logError, handleValidationError
|
||||||
|
│ │ ├── types.ts # Router{Client,Inputs,Outputs} derived from Contract
|
||||||
│ │ ├── contracts/ # Zod schemas from Drizzle tables (barrel: contract)
|
│ │ ├── contracts/ # Zod schemas from Drizzle tables (barrel: contract)
|
||||||
│ │ ├── routers/ # os.* handlers (barrel: router)
|
│ │ └── routers/ # os.* handlers (barrel: router) — import db directly
|
||||||
│ │ └── middlewares/ # db middleware injects getDB() singleton
|
|
||||||
│ ├── db/
|
│ ├── db/
|
||||||
│ │ ├── index.ts # createDB({ connection, schema }) + getDB()/closeDB() singleton
|
│ │ ├── index.ts # module-level `export const db = drizzle({...})`
|
||||||
│ │ ├── fields.ts # pk (UUIDv7), createdAt, updatedAt, generatedFields(Keys)
|
│ │ ├── fields.ts # generatedFields (id/createdAt/updatedAt) + generatedFieldKeys
|
||||||
│ │ └── schema/ # pgTable definitions; also put `relations()` here when adding
|
│ │ └── schema/ # pgTable definitions; also put `relations()` here when adding
|
||||||
│ └── plugins/
|
│ └── 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
|
├── env.ts # t3-oss env validation
|
||||||
├── router.tsx # QueryClient + setupRouterSsrQueryIntegration
|
├── router.tsx # QueryClient + setupRouterSsrQueryIntegration
|
||||||
└── routeTree.gen.ts # auto-generated, do not edit
|
└── 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 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 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 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 `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 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.
|
- Don't use RQB v2 object syntax, `defineRelations`, or pass `relations` to `drizzle()`. All are 1.0 beta.
|
||||||
|
|||||||
Reference in New Issue
Block a user