diff --git a/AGENTS.md b/AGENTS.md index 27c3c8b..026180a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/apps/server/AGENTS.md b/apps/server/AGENTS.md index 4078225..0bbe1a6 100644 --- a/apps/server/AGENTS.md +++ b/apps/server/AGENTS.md @@ -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()) diff --git a/apps/server/src/client/query-client.ts b/apps/server/src/client/query-client.ts index 38acc75..fea2f07 100644 --- a/apps/server/src/client/query-client.ts +++ b/apps/server/src/client/query-client.ts @@ -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: { diff --git a/apps/server/src/components/NotFount.tsx b/apps/server/src/components/NotFound.tsx similarity index 100% rename from apps/server/src/components/NotFount.tsx rename to apps/server/src/components/NotFound.tsx diff --git a/apps/server/src/routes/__root.tsx b/apps/server/src/routes/__root.tsx index 4baa411..3ff843d 100644 --- a/apps/server/src/routes/__root.tsx +++ b/apps/server/src/routes/__root.tsx @@ -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 { diff --git a/apps/server/src/routes/api/$.ts b/apps/server/src/routes/api/$.ts index 95121a4..5246b3d 100644 --- a/apps/server/src/routes/api/$.ts +++ b/apps/server/src/routes/api/$.ts @@ -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/$')({ diff --git a/apps/server/src/routes/api/rpc.$.ts b/apps/server/src/routes/api/rpc.$.ts index 910bba9..fbf3266 100644 --- a/apps/server/src/routes/api/rpc.$.ts +++ b/apps/server/src/routes/api/rpc.$.ts @@ -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/$')({ diff --git a/apps/server/src/server/api/interceptors.ts b/apps/server/src/server/api/interceptors.ts new file mode 100644 index 0000000..fc9183f --- /dev/null +++ b/apps/server/src/server/api/interceptors.ts @@ -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, + }) + } +} diff --git a/apps/server/src/server/db/schema/utils/field.ts b/apps/server/src/server/db/fields.ts similarity index 100% rename from apps/server/src/server/db/schema/utils/field.ts rename to apps/server/src/server/db/fields.ts diff --git a/apps/server/src/server/db/schema/todo.ts b/apps/server/src/server/db/schema/todo.ts index d6ee127..5ca821c 100644 --- a/apps/server/src/server/db/schema/todo.ts +++ b/apps/server/src/server/db/schema/todo.ts @@ -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, diff --git a/bun.lock b/bun.lock index e8f1894..dad85ed 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], diff --git a/packages/utils/package.json b/packages/utils/package.json deleted file mode 100644 index b4d5e6d..0000000 --- a/packages/utils/package.json +++ /dev/null @@ -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:*" - } -} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts deleted file mode 100644 index 336ce12..0000000 --- a/packages/utils/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}