Skip to content

Commit ebf0ed6

Browse files
committed
Add agent guidelines and update dependencies: Introduce AGENTS.md for project structure and best practices. Update package.json and pnpm-lock.yaml to include new dependencies like @tanstack/react-query and openapi-fetch, and remove unused prisma configuration.
1 parent 18d90de commit ebf0ed6

File tree

16 files changed

+617
-63
lines changed

16 files changed

+617
-63
lines changed

AGENTS.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Agent Guidelines
2+
3+
## Project Structure
4+
5+
- **Backend**: `apps/api/` - NestJS with Drizzle ORM
6+
- **Frontend**: `apps/web/` - TanStack Start
7+
- **Shared**: `packages/typescript-config/` - Shared TypeScript configs
8+
9+
## Frontend
10+
11+
### Routing (TanStack Start)
12+
13+
- Use Tanstack Start best practices
14+
15+
### Data Fetching (TanStack Query)
16+
17+
- Query keys: `['resource', id, 'sub-resource']`
18+
- Use `invalidateQueries()` without parameters
19+
20+
### Forms (TanStack Forms)
21+
22+
- Use `useForm()`, ShadCN Field components (`Field`, `FieldLabel`, `FieldError`), `form.useField()` for fields
23+
24+
### UI Components (ShadCN + BaseUI)
25+
26+
- Components: `apps/web/src/components/ui/` - ShadCN
27+
- Prefer BaseUI primitives over raw html (`@base-ui/react`)
28+
- Utils: `apps/web/src/lib/utils.ts` - `cn()` helper
29+
- Tailwind CSS with ShadCN design tokens
30+
31+
### Type Safety
32+
33+
- E2E types: `apps/api/generated/openapi.d.ts` (auto-generated)
34+
35+
### Creating Routes/Components
36+
37+
- Routes: `apps/web/src/routes/{path}.tsx` with `createFileRoute()`
38+
- Components: `apps/web/src/components/` using ShadCN UI + `cn()`
39+
40+
## Backend
41+
42+
### Database
43+
44+
- Schema: `apps/api/src/databases/drizzle.schema.ts`
45+
- Tables: `apps/api/src/databases/tables/*.table.ts`
46+
- Utils: `apps/api/src/databases/drizzle.utils.ts`
47+
- Provider: `apps/api/src/databases/drizzle.provider.ts`
48+
49+
### Authentication
50+
51+
- Provider: `apps/api/src/auth/better-auth.provider.ts`
52+
- Guard: `apps/api/src/auth/guards/auth.guard.ts`
53+
- Decorators: `@Auth()`, `@ActiveUser()`, `@ActiveSession()`
54+
55+
### Transactions
56+
57+
- Use `@Transactional()` decorator
58+
- Inject `TransactionHost<DrizzleTransactionClient>`
59+
- Don't manually wrap with `tx.transaction()`
60+
61+
### Services
62+
63+
- Providers for external clients (Better Auth, Drizzle)
64+
- Services handle business logic
65+
- Export providers with `Inject*()` helpers
66+
67+
### Error Handling
68+
69+
- Use NestJS exceptions (`NotFoundException`, `UnauthorizedException`, etc.)
70+
71+
### Type Safety
72+
73+
- E2E types: `apps/api/generated/openapi.d.ts` (auto-generated)
74+
- Use `Serialize` decorator for DTO transformation
75+
76+
### Creating Tables
77+
78+
1. Create `apps/api/src/databases/tables/{name}.table.ts`
79+
2. Export from `drizzle.schema.ts`
80+
3. Add relations in `drizzle.relations.ts`
81+
82+
## Important Notes
83+
84+
- Don't nest transactions - use `@Transactional()` decorator
85+
- Avoid `as any` - use proper types or `as` with specific types

apps/api/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@
9393
"jest": "^30.2.0",
9494
"openapi-typescript": "^7.10.1",
9595
"pnpm": "^10.27.0",
96-
"prisma": "^7.2.0",
9796
"react-email": "5.1.1",
9897
"source-map-support": "^0.5.21",
9998
"supertest": "^7.1.4",

apps/api/prisma.config.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { UseInterceptors } from '@nestjs/common';
2+
import { ClassConstructor } from 'class-transformer';
3+
import { TransformDataInterceptor } from '../interceptors/transform-data.interceptor';
4+
5+
/**
6+
* Decorator that applies data transformation using class-transformer
7+
* @param classToUse - The class to transform the response data to
8+
*/
9+
export const Serialize = (
10+
classToUse: ClassConstructor<unknown>,
11+
): MethodDecorator => {
12+
return UseInterceptors(new TransformDataInterceptor(classToUse));
13+
};

apps/web/package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616
"@fontsource-variable/jetbrains-mono": "^5.2.8",
1717
"@tailwindcss/vite": "^4.0.6",
1818
"@tanstack/react-devtools": "^0.7.0",
19+
"@tanstack/react-query": "^5.90.16",
1920
"@tanstack/react-router": "^1.132.0",
2021
"@tanstack/react-router-devtools": "^1.132.0",
2122
"@tanstack/react-router-ssr-query": "^1.131.7",
2223
"@tanstack/react-start": "^1.132.0",
2324
"@tanstack/router-plugin": "^1.132.0",
25+
"api": "workspace:*",
26+
"better-auth": "^1.4.10",
2427
"class-variance-authority": "^0.7.1",
2528
"clsx": "^2.1.1",
2629
"lucide-react": "^0.562.0",
2730
"nitro": "latest",
31+
"openapi-fetch": "^0.15.0",
2832
"react": "^19.2.0",
2933
"react-dom": "^19.2.0",
3034
"shadcn": "^3.6.2",
@@ -36,17 +40,20 @@
3640
"devDependencies": {
3741
"@tanstack/devtools-vite": "^0.3.11",
3842
"@tanstack/eslint-config": "^0.3.0",
43+
"@tanstack/react-form-devtools": "^0.2.10",
44+
"@tanstack/react-query-devtools": "^5.91.2",
3945
"@testing-library/dom": "^10.4.0",
4046
"@testing-library/react": "^16.2.0",
4147
"@types/node": "^22.10.2",
4248
"@types/react": "^19.2.0",
4349
"@types/react-dom": "^19.2.0",
4450
"@vitejs/plugin-react": "^5.0.4",
4551
"jsdom": "^27.0.0",
52+
"openapi-typescript": "^7.10.1",
4653
"prettier": "^3.5.3",
47-
"typescript": "^5.7.2",
54+
"typescript": "5.9.3",
4855
"vite": "^7.1.7",
4956
"vitest": "^3.0.5",
5057
"web-vitals": "^5.1.0"
5158
}
52-
}
59+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {
2+
ErrorComponent,
3+
Link,
4+
rootRouteId,
5+
useMatch,
6+
useRouter,
7+
} from '@tanstack/react-router'
8+
import type { ErrorComponentProps } from '@tanstack/react-router'
9+
10+
export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
11+
const router = useRouter()
12+
const isRoot = useMatch({
13+
strict: false,
14+
select: (state) => state.id === rootRouteId,
15+
})
16+
17+
console.error(error)
18+
19+
return (
20+
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
21+
<ErrorComponent error={error} />
22+
<div className="flex gap-2 items-center flex-wrap">
23+
<button
24+
onClick={() => {
25+
router.invalidate()
26+
}}
27+
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
28+
>
29+
Try Again
30+
</button>
31+
{isRoot ? (
32+
<Link
33+
to="/"
34+
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
35+
>
36+
Home
37+
</Link>
38+
) : (
39+
<Link
40+
to="/"
41+
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
42+
onClick={(e) => {
43+
e.preventDefault()
44+
window.history.back()
45+
}}
46+
>
47+
Go Back
48+
</Link>
49+
)}
50+
</div>
51+
</div>
52+
)
53+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Link } from '@tanstack/react-router'
2+
3+
export function NotFound({ children }: { children?: any }) {
4+
return (
5+
<div className="space-y-2 p-2">
6+
<div className="text-gray-600 dark:text-gray-400">
7+
{children || <p>The page you are looking for does not exist.</p>}
8+
</div>
9+
<p className="flex items-center gap-2 flex-wrap">
10+
<button
11+
onClick={() => window.history.back()}
12+
className="bg-emerald-500 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
13+
>
14+
Go back
15+
</button>
16+
<Link
17+
to="/"
18+
className="bg-cyan-600 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
19+
>
20+
Start Over
21+
</Link>
22+
</p>
23+
</div>
24+
)
25+
}

apps/web/src/lib/api-client.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import createClient, { Middleware } from 'openapi-fetch'
2+
import { getRequest } from '@tanstack/react-start/server'
3+
import { createIsomorphicFn } from '@tanstack/react-start'
4+
import { paths } from 'api/generated/openapi'
5+
6+
const middleware: Middleware = {
7+
async onResponse({ response }) {
8+
if (!response.ok) {
9+
throw new Error(
10+
`${response.url}: ${response.status} ${response.statusText}`,
11+
)
12+
}
13+
},
14+
}
15+
16+
export function apiClient() {
17+
const apiUrl = import.meta.env.VITE_API_URL
18+
19+
return createIsomorphicFn()
20+
.server(() => {
21+
const { headers } = getRequest()
22+
const client = createClient<paths>({
23+
baseUrl: apiUrl,
24+
headers: Object.fromEntries(headers),
25+
credentials: 'include',
26+
})
27+
client.use(middleware)
28+
29+
return client
30+
})
31+
.client(() => {
32+
const client = createClient<paths>({
33+
baseUrl: apiUrl,
34+
fetch: (input: RequestInfo | URL, init?: RequestInit) => {
35+
// const sessionId = posthog.get_session_id?.();
36+
return fetch(input, {
37+
...init,
38+
credentials: 'include',
39+
headers: {
40+
...init?.headers,
41+
...Object.fromEntries((input as Request).headers),
42+
// ...(sessionId && { "x-posthog-session-id": sessionId }),
43+
},
44+
})
45+
},
46+
})
47+
client.use(middleware)
48+
return client
49+
})()
50+
}

apps/web/src/lib/api/auth.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { authClient } from '../auth-client'
2+
3+
interface ApiHandler {
4+
queryKeys: string[]
5+
}
6+
7+
export class AuthApi implements ApiHandler {
8+
readonly queryKeys = ['auth']
9+
10+
async getUserQueryOptions() {}
11+
}

apps/web/src/lib/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// export const api = {
2+
// auth:
3+
// }

0 commit comments

Comments
 (0)