refactor: 优化项目结构 — 修复拼写、提取共享 interceptor、扁平化 db 目录、清理空包

This commit is contained in:
2026-03-05 10:58:55 +08:00
parent 0438b52c93
commit 0cd8b57d24
13 changed files with 55 additions and 121 deletions

View File

@@ -12,7 +12,7 @@ Guidelines for AI agents working in this Bun monorepo.
- **Apps**:
- `apps/server` - TanStack Start fullstack web app (see `apps/server/AGENTS.md`)
- `apps/desktop` - Electron desktop shell, sidecar server pattern (see `apps/desktop/AGENTS.md`)
- **Packages**: `packages/utils`, `packages/tsconfig` (shared configs)
- **Packages**: `packages/tsconfig` (shared TS configs)
## Build / Lint / Test Commands
@@ -207,8 +207,7 @@ export const myTable = pgTable('my_table', {
│ ├── electron-builder.yml # Packaging config
│ └── AGENTS.md
├── packages/
── tsconfig/ # Shared TS configs
│ └── utils/ # Shared utilities
── tsconfig/ # Shared TS configs
├── biome.json # Linting/formatting config
├── turbo.json # Turbo task orchestration
└── package.json # Workspace root + dependency catalog

View File

@@ -54,24 +54,28 @@ bun test -t "pattern" # Run tests matching pattern
```
src/
├── client/ # Client-side code
│ ├── orpc.ts # ORPC isomorphic client
│ └── query-client.ts # TanStack Query client
│ ├── orpc.ts # ORPC isomorphic client (internal)
│ └── query-client.ts # TanStack Query utils (used by components)
├── components/ # React components
├── routes/ # TanStack Router file routes
│ ├── __root.tsx # Root layout
│ ├── index.tsx # Home page
│ └── api/
── rpc.$.ts # ORPC HTTP endpoint
── $.ts # OpenAPI handler + Scalar docs
│ ├── health.ts # Health check endpoint
│ └── rpc.$.ts # ORPC RPC handler
├── server/ # Server-side code
│ ├── api/ # ORPC layer
│ │ ├── contracts/ # Input/output schemas (Zod)
│ │ ├── middlewares/ # Middleware (db provider, auth)
│ │ ├── routers/ # Handler implementations
│ │ ├── interceptors.ts # Shared error interceptors
│ │ ├── context.ts # Request context
│ │ ├── server.ts # ORPC server instance
│ │ └── types.ts # Type exports
│ └── db/
│ ├── schema/ # Drizzle table definitions
│ ├── fields.ts # Shared field builders (id, createdAt, updatedAt)
│ ├── relations.ts # Drizzle relations (defineRelations, RQBv2)
│ └── index.ts # Database instance (postgres-js driver)
├── env.ts # Environment variable validation
@@ -122,7 +126,7 @@ export const router = os.router({ feature })
### 4. Use in Components
```typescript
import { useSuspenseQuery, useMutation } from '@tanstack/react-query'
import { orpc } from '@/client/orpc'
import { orpc } from '@/client/query-client'
const { data } = useSuspenseQuery(orpc.feature.list.queryOptions())
const mutation = useMutation(orpc.feature.create.mutationOptions())

View File

@@ -1,7 +1,7 @@
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
import { orpc as orpcClient } from './orpc'
import { orpc as client } from './orpc'
export const orpc = createTanstackQueryUtils(orpcClient, {
export const orpc = createTanstackQueryUtils(client, {
experimental_defaults: {
todo: {
create: {

View File

@@ -9,7 +9,7 @@ import {
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import type { ReactNode } from 'react'
import { ErrorComponent } from '@/components/Error'
import { NotFoundComponent } from '@/components/NotFount'
import { NotFoundComponent } from '@/components/NotFound'
import appCss from '@/styles.css?url'
export interface RouterContext {

View File

@@ -1,10 +1,10 @@
import { OpenAPIHandler } from '@orpc/openapi/fetch'
import { OpenAPIReferencePlugin } from '@orpc/openapi/plugins'
import { ORPCError, onError, ValidationError } from '@orpc/server'
import { onError } from '@orpc/server'
import { ZodToJsonSchemaConverter } from '@orpc/zod/zod4'
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
import { name, version } from '@/../package.json'
import { handleValidationError, logError } from '@/server/api/interceptors'
import { router } from '@/server/api/routers'
const handler = new OpenAPIHandler(router, {
@@ -17,55 +17,13 @@ const handler = new OpenAPIHandler(router, {
title: name,
version,
},
// components: {
// securitySchemes: {
// bearerAuth: {
// type: 'http',
// scheme: 'bearer',
// },
// },
// },
},
docsPath: '/docs',
specPath: '/spec.json',
}),
],
interceptors: [
onError((error) => {
console.error(error)
}),
],
clientInterceptors: [
onError((error) => {
if (
error instanceof ORPCError &&
error.code === 'BAD_REQUEST' &&
error.cause instanceof ValidationError
) {
// If you only use Zod you can safely cast to ZodIssue[]
const zodError = new z.ZodError(
error.cause.issues as z.core.$ZodIssue[],
)
throw new ORPCError('INPUT_VALIDATION_FAILED', {
status: 422,
message: z.prettifyError(zodError),
data: z.flattenError(zodError),
cause: error.cause,
})
}
if (
error instanceof ORPCError &&
error.code === 'INTERNAL_SERVER_ERROR' &&
error.cause instanceof ValidationError
) {
throw new ORPCError('OUTPUT_VALIDATION_FAILED', {
cause: error.cause,
})
}
}),
],
interceptors: [onError(logError)],
clientInterceptors: [onError(handleValidationError)],
})
export const Route = createFileRoute('/api/$')({

View File

@@ -1,46 +1,12 @@
import { ORPCError, onError, ValidationError } from '@orpc/server'
import { onError } from '@orpc/server'
import { RPCHandler } from '@orpc/server/fetch'
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
import { handleValidationError, logError } from '@/server/api/interceptors'
import { router } from '@/server/api/routers'
const handler = new RPCHandler(router, {
interceptors: [
onError((error) => {
console.error(error)
}),
],
clientInterceptors: [
onError((error) => {
if (
error instanceof ORPCError &&
error.code === 'BAD_REQUEST' &&
error.cause instanceof ValidationError
) {
// If you only use Zod you can safely cast to ZodIssue[]
const zodError = new z.ZodError(
error.cause.issues as z.core.$ZodIssue[],
)
throw new ORPCError('INPUT_VALIDATION_FAILED', {
status: 422,
message: z.prettifyError(zodError),
data: z.flattenError(zodError),
cause: error.cause,
})
}
if (
error instanceof ORPCError &&
error.code === 'INTERNAL_SERVER_ERROR' &&
error.cause instanceof ValidationError
) {
throw new ORPCError('OUTPUT_VALIDATION_FAILED', {
cause: error.cause,
})
}
}),
],
interceptors: [onError(logError)],
clientInterceptors: [onError(handleValidationError)],
})
export const Route = createFileRoute('/api/rpc/$')({

View File

@@ -0,0 +1,33 @@
import { ORPCError, ValidationError } from '@orpc/server'
import { z } from 'zod'
export const logError = (error: unknown) => {
console.error(error)
}
export const handleValidationError = (error: unknown) => {
if (
error instanceof ORPCError &&
error.code === 'BAD_REQUEST' &&
error.cause instanceof ValidationError
) {
const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[])
throw new ORPCError('INPUT_VALIDATION_FAILED', {
status: 422,
message: z.prettifyError(zodError),
data: z.flattenError(zodError),
cause: error.cause,
})
}
if (
error instanceof ORPCError &&
error.code === 'INTERNAL_SERVER_ERROR' &&
error.cause instanceof ValidationError
) {
throw new ORPCError('OUTPUT_VALIDATION_FAILED', {
cause: error.cause,
})
}
}

View File

@@ -1,5 +1,5 @@
import { boolean, pgTable, text } from 'drizzle-orm/pg-core'
import { generatedFields } from './utils/field'
import { generatedFields } from '../fields'
export const todoTable = pgTable('todo', {
...generatedFields,

View File

@@ -74,13 +74,6 @@
"name": "@furtherverse/tsconfig",
"version": "1.0.0",
},
"packages/utils": {
"name": "@furtherverse/utils",
"version": "1.0.0",
"devDependencies": {
"@furtherverse/tsconfig": "workspace:*",
},
},
},
"overrides": {
"@types/node": "catalog:",
@@ -309,8 +302,6 @@
"@furtherverse/tsconfig": ["@furtherverse/tsconfig@workspace:packages/tsconfig"],
"@furtherverse/utils": ["@furtherverse/utils@workspace:packages/utils"],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],

View File

@@ -1,16 +0,0 @@
{
"name": "@furtherverse/utils",
"version": "1.0.0",
"private": true,
"type": "module",
"imports": {
"#*": "./src/*"
},
"exports": {
".": "./src/index.ts",
"./*": "./src/*.ts"
},
"devDependencies": {
"@furtherverse/tsconfig": "workspace:*"
}
}

View File

@@ -1 +0,0 @@
export {}