agentic-team-templates 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# API Contracts
|
|
2
|
+
|
|
3
|
+
Defining and maintaining contracts between frontend and backend.
|
|
4
|
+
|
|
5
|
+
## Why API Contracts Matter
|
|
6
|
+
|
|
7
|
+
- **Type Safety**: Catch mismatches at compile time
|
|
8
|
+
- **Documentation**: Self-documenting API surface
|
|
9
|
+
- **Consistency**: Single source of truth
|
|
10
|
+
- **Parallel Development**: Frontend and backend can work independently
|
|
11
|
+
|
|
12
|
+
## Schema-Driven Development
|
|
13
|
+
|
|
14
|
+
### Define Schemas First
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// shared/schemas/user.ts
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
|
|
20
|
+
// Request schemas
|
|
21
|
+
export const CreateUserRequestSchema = z.object({
|
|
22
|
+
email: z.string().email(),
|
|
23
|
+
name: z.string().min(1).max(100),
|
|
24
|
+
role: z.enum(['user', 'admin']).default('user'),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const UpdateUserRequestSchema = CreateUserRequestSchema.partial();
|
|
28
|
+
|
|
29
|
+
// Response schemas
|
|
30
|
+
export const UserResponseSchema = z.object({
|
|
31
|
+
id: z.string(),
|
|
32
|
+
email: z.string(),
|
|
33
|
+
name: z.string(),
|
|
34
|
+
role: z.enum(['user', 'admin']),
|
|
35
|
+
createdAt: z.string().datetime(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const UsersListResponseSchema = z.object({
|
|
39
|
+
data: z.array(UserResponseSchema),
|
|
40
|
+
pagination: z.object({
|
|
41
|
+
page: z.number(),
|
|
42
|
+
limit: z.number(),
|
|
43
|
+
total: z.number(),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Infer types from schemas
|
|
48
|
+
export type CreateUserRequest = z.infer<typeof CreateUserRequestSchema>;
|
|
49
|
+
export type UpdateUserRequest = z.infer<typeof UpdateUserRequestSchema>;
|
|
50
|
+
export type UserResponse = z.infer<typeof UserResponseSchema>;
|
|
51
|
+
export type UsersListResponse = z.infer<typeof UsersListResponseSchema>;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Use on Backend
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// api/routes/users.ts
|
|
58
|
+
import { CreateUserRequestSchema, UserResponseSchema } from '@shared/schemas/user';
|
|
59
|
+
|
|
60
|
+
app.post('/users', async (req, res) => {
|
|
61
|
+
// Validate input
|
|
62
|
+
const parsed = CreateUserRequestSchema.safeParse(req.body);
|
|
63
|
+
if (!parsed.success) {
|
|
64
|
+
return res.status(422).json({ error: parsed.error });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const user = await createUser(parsed.data);
|
|
68
|
+
|
|
69
|
+
// Validate output (optional but good for catching bugs)
|
|
70
|
+
const response = UserResponseSchema.parse(user);
|
|
71
|
+
res.status(201).json({ data: response });
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Use on Frontend
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// lib/api/users.ts
|
|
79
|
+
import {
|
|
80
|
+
CreateUserRequest,
|
|
81
|
+
UserResponse,
|
|
82
|
+
UsersListResponse,
|
|
83
|
+
CreateUserRequestSchema,
|
|
84
|
+
} from '@shared/schemas/user';
|
|
85
|
+
|
|
86
|
+
export const usersApi = {
|
|
87
|
+
async create(data: CreateUserRequest): Promise<UserResponse> {
|
|
88
|
+
// Validate before sending (catches client-side errors early)
|
|
89
|
+
const validated = CreateUserRequestSchema.parse(data);
|
|
90
|
+
|
|
91
|
+
const response = await fetch('/api/users', {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: { 'Content-Type': 'application/json' },
|
|
94
|
+
body: JSON.stringify(validated),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new ApiError(response);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const json = await response.json();
|
|
102
|
+
return json.data;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async list(page = 1, limit = 20): Promise<UsersListResponse> {
|
|
106
|
+
const response = await fetch(`/api/users?page=${page}&limit=${limit}`);
|
|
107
|
+
if (!response.ok) throw new ApiError(response);
|
|
108
|
+
return response.json();
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## OpenAPI / Swagger
|
|
114
|
+
|
|
115
|
+
### Generate from Code
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// Using Zod to OpenAPI
|
|
119
|
+
import { OpenAPIRegistry, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';
|
|
120
|
+
|
|
121
|
+
const registry = new OpenAPIRegistry();
|
|
122
|
+
|
|
123
|
+
registry.registerPath({
|
|
124
|
+
method: 'post',
|
|
125
|
+
path: '/users',
|
|
126
|
+
summary: 'Create a new user',
|
|
127
|
+
request: {
|
|
128
|
+
body: {
|
|
129
|
+
content: { 'application/json': { schema: CreateUserRequestSchema } },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
responses: {
|
|
133
|
+
201: {
|
|
134
|
+
description: 'User created',
|
|
135
|
+
content: { 'application/json': { schema: UserResponseSchema } },
|
|
136
|
+
},
|
|
137
|
+
422: { description: 'Validation error' },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const generator = new OpenApiGeneratorV3(registry.definitions);
|
|
142
|
+
export const openApiDoc = generator.generateDocument({
|
|
143
|
+
info: { title: 'Users API', version: '1.0.0' },
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Manual OpenAPI Spec
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
# openapi.yaml
|
|
151
|
+
openapi: 3.0.0
|
|
152
|
+
info:
|
|
153
|
+
title: Users API
|
|
154
|
+
version: 1.0.0
|
|
155
|
+
|
|
156
|
+
paths:
|
|
157
|
+
/users:
|
|
158
|
+
post:
|
|
159
|
+
summary: Create a new user
|
|
160
|
+
requestBody:
|
|
161
|
+
required: true
|
|
162
|
+
content:
|
|
163
|
+
application/json:
|
|
164
|
+
schema:
|
|
165
|
+
$ref: '#/components/schemas/CreateUserRequest'
|
|
166
|
+
responses:
|
|
167
|
+
'201':
|
|
168
|
+
description: User created
|
|
169
|
+
content:
|
|
170
|
+
application/json:
|
|
171
|
+
schema:
|
|
172
|
+
$ref: '#/components/schemas/UserResponse'
|
|
173
|
+
|
|
174
|
+
components:
|
|
175
|
+
schemas:
|
|
176
|
+
CreateUserRequest:
|
|
177
|
+
type: object
|
|
178
|
+
required: [email, name]
|
|
179
|
+
properties:
|
|
180
|
+
email:
|
|
181
|
+
type: string
|
|
182
|
+
format: email
|
|
183
|
+
name:
|
|
184
|
+
type: string
|
|
185
|
+
minLength: 1
|
|
186
|
+
maxLength: 100
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Type-Safe API Clients
|
|
190
|
+
|
|
191
|
+
### Using tRPC
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// server/trpc.ts
|
|
195
|
+
import { initTRPC } from '@trpc/server';
|
|
196
|
+
import { z } from 'zod';
|
|
197
|
+
|
|
198
|
+
const t = initTRPC.create();
|
|
199
|
+
|
|
200
|
+
export const appRouter = t.router({
|
|
201
|
+
user: t.router({
|
|
202
|
+
list: t.procedure
|
|
203
|
+
.input(z.object({ page: z.number().default(1) }))
|
|
204
|
+
.query(async ({ input }) => {
|
|
205
|
+
return db.user.findMany({ skip: (input.page - 1) * 20, take: 20 });
|
|
206
|
+
}),
|
|
207
|
+
|
|
208
|
+
create: t.procedure
|
|
209
|
+
.input(CreateUserRequestSchema)
|
|
210
|
+
.mutation(async ({ input }) => {
|
|
211
|
+
return db.user.create({ data: input });
|
|
212
|
+
}),
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export type AppRouter = typeof appRouter;
|
|
217
|
+
|
|
218
|
+
// client/trpc.ts
|
|
219
|
+
import { createTRPCReact } from '@trpc/react-query';
|
|
220
|
+
import type { AppRouter } from '../server/trpc';
|
|
221
|
+
|
|
222
|
+
export const trpc = createTRPCReact<AppRouter>();
|
|
223
|
+
|
|
224
|
+
// Usage in component
|
|
225
|
+
const users = trpc.user.list.useQuery({ page: 1 });
|
|
226
|
+
const createUser = trpc.user.create.useMutation();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Type-Safe Fetch Wrapper
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// lib/api/client.ts
|
|
233
|
+
type ApiResponse<T> = { data: T } | { error: { code: string; message: string } };
|
|
234
|
+
|
|
235
|
+
async function apiRequest<T>(
|
|
236
|
+
path: string,
|
|
237
|
+
options?: RequestInit
|
|
238
|
+
): Promise<T> {
|
|
239
|
+
const response = await fetch(`/api${path}`, {
|
|
240
|
+
...options,
|
|
241
|
+
headers: {
|
|
242
|
+
'Content-Type': 'application/json',
|
|
243
|
+
...options?.headers,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const json: ApiResponse<T> = await response.json();
|
|
248
|
+
|
|
249
|
+
if ('error' in json) {
|
|
250
|
+
throw new ApiError(json.error.code, json.error.message);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return json.data;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Typed endpoints
|
|
257
|
+
export const api = {
|
|
258
|
+
users: {
|
|
259
|
+
list: () => apiRequest<UsersListResponse>('/users'),
|
|
260
|
+
get: (id: string) => apiRequest<UserResponse>(`/users/${id}`),
|
|
261
|
+
create: (data: CreateUserRequest) =>
|
|
262
|
+
apiRequest<UserResponse>('/users', {
|
|
263
|
+
method: 'POST',
|
|
264
|
+
body: JSON.stringify(data),
|
|
265
|
+
}),
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Versioning
|
|
271
|
+
|
|
272
|
+
### URL Versioning
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
/api/v1/users
|
|
276
|
+
/api/v2/users
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Backwards Compatibility
|
|
280
|
+
|
|
281
|
+
When evolving APIs:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
// v1 response
|
|
285
|
+
interface UserV1 {
|
|
286
|
+
id: string;
|
|
287
|
+
name: string; // Full name
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// v2 response (split name)
|
|
291
|
+
interface UserV2 {
|
|
292
|
+
id: string;
|
|
293
|
+
firstName: string;
|
|
294
|
+
lastName: string;
|
|
295
|
+
name: string; // Keep for backwards compatibility
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Transformer for v1 clients
|
|
299
|
+
const toV1Response = (user: UserV2): UserV1 => ({
|
|
300
|
+
id: user.id,
|
|
301
|
+
name: user.name,
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Contract Testing
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
// Verify backend implements contract correctly
|
|
309
|
+
describe('Users API Contract', () => {
|
|
310
|
+
it('POST /users returns valid UserResponse', async () => {
|
|
311
|
+
const response = await request(app)
|
|
312
|
+
.post('/users')
|
|
313
|
+
.send({ email: 'test@example.com', name: 'Test' });
|
|
314
|
+
|
|
315
|
+
expect(response.status).toBe(201);
|
|
316
|
+
|
|
317
|
+
// Validate against schema
|
|
318
|
+
const result = UserResponseSchema.safeParse(response.body.data);
|
|
319
|
+
expect(result.success).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('GET /users returns valid UsersListResponse', async () => {
|
|
323
|
+
const response = await request(app).get('/users');
|
|
324
|
+
|
|
325
|
+
expect(response.status).toBe(200);
|
|
326
|
+
|
|
327
|
+
const result = UsersListResponseSchema.safeParse(response.body);
|
|
328
|
+
expect(result.success).toBe(true);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
```
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Fullstack Architecture
|
|
2
|
+
|
|
3
|
+
Architectural patterns for full-stack applications.
|
|
4
|
+
|
|
5
|
+
## Architectural Layers
|
|
6
|
+
|
|
7
|
+
### Presentation Layer (Frontend)
|
|
8
|
+
- Components and pages
|
|
9
|
+
- State management
|
|
10
|
+
- User interactions
|
|
11
|
+
- Client-side routing
|
|
12
|
+
|
|
13
|
+
### Application Layer (API)
|
|
14
|
+
- Route handlers
|
|
15
|
+
- Request validation
|
|
16
|
+
- Authentication/authorization
|
|
17
|
+
- Response formatting
|
|
18
|
+
|
|
19
|
+
### Domain Layer (Business Logic)
|
|
20
|
+
- Business rules
|
|
21
|
+
- Domain models
|
|
22
|
+
- Use cases/services
|
|
23
|
+
- Pure functions
|
|
24
|
+
|
|
25
|
+
### Infrastructure Layer
|
|
26
|
+
- Database access
|
|
27
|
+
- External API clients
|
|
28
|
+
- File storage
|
|
29
|
+
- Caching
|
|
30
|
+
|
|
31
|
+
## Layer Boundaries
|
|
32
|
+
|
|
33
|
+
### Dependency Direction
|
|
34
|
+
|
|
35
|
+
Dependencies should point inward (toward domain):
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Presentation → Application → Domain ← Infrastructure
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Example
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// Domain layer (no dependencies on outer layers)
|
|
45
|
+
// domain/user.ts
|
|
46
|
+
export interface User {
|
|
47
|
+
id: string;
|
|
48
|
+
email: string;
|
|
49
|
+
name: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface UserRepository {
|
|
53
|
+
findById(id: string): Promise<User | null>;
|
|
54
|
+
create(data: CreateUserInput): Promise<User>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const createUser = async (
|
|
58
|
+
repo: UserRepository,
|
|
59
|
+
input: CreateUserInput
|
|
60
|
+
): Promise<User> => {
|
|
61
|
+
// Business logic here
|
|
62
|
+
const validated = validateUserInput(input);
|
|
63
|
+
return repo.create(validated);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Infrastructure layer (implements domain interfaces)
|
|
67
|
+
// infrastructure/userRepository.ts
|
|
68
|
+
import { db } from './db';
|
|
69
|
+
import { UserRepository } from '../domain/user';
|
|
70
|
+
|
|
71
|
+
export const prismaUserRepository: UserRepository = {
|
|
72
|
+
findById: (id) => db.user.findUnique({ where: { id } }),
|
|
73
|
+
create: (data) => db.user.create({ data }),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Application layer (coordinates domain and infrastructure)
|
|
77
|
+
// api/users.ts
|
|
78
|
+
import { createUser } from '../domain/user';
|
|
79
|
+
import { prismaUserRepository } from '../infrastructure/userRepository';
|
|
80
|
+
|
|
81
|
+
export const handleCreateUser = async (req, res) => {
|
|
82
|
+
const result = await createUser(prismaUserRepository, req.body);
|
|
83
|
+
res.json({ data: result });
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Server Components vs Client Components
|
|
88
|
+
|
|
89
|
+
### Server Components (Default)
|
|
90
|
+
- Render on server
|
|
91
|
+
- Can access database directly
|
|
92
|
+
- No interactivity
|
|
93
|
+
- No client-side state
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// app/users/page.tsx (Server Component)
|
|
97
|
+
import { db } from '@/lib/server/db';
|
|
98
|
+
|
|
99
|
+
export default async function UsersPage() {
|
|
100
|
+
const users = await db.user.findMany(); // Direct DB access
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<ul>
|
|
104
|
+
{users.map(user => (
|
|
105
|
+
<li key={user.id}>{user.name}</li>
|
|
106
|
+
))}
|
|
107
|
+
</ul>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Client Components
|
|
113
|
+
- Run in browser
|
|
114
|
+
- Handle interactivity
|
|
115
|
+
- Manage client state
|
|
116
|
+
- Use hooks
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// components/UserForm.tsx
|
|
120
|
+
'use client';
|
|
121
|
+
|
|
122
|
+
import { useState } from 'react';
|
|
123
|
+
|
|
124
|
+
export function UserForm({ onSubmit }: { onSubmit: (data: FormData) => void }) {
|
|
125
|
+
const [name, setName] = useState('');
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<form onSubmit={(e) => {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
onSubmit({ name });
|
|
131
|
+
}}>
|
|
132
|
+
<input value={name} onChange={(e) => setName(e.target.value)} />
|
|
133
|
+
<button type="submit">Submit</button>
|
|
134
|
+
</form>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Composition Pattern
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
// Server component fetches data
|
|
143
|
+
// app/users/[id]/page.tsx
|
|
144
|
+
import { db } from '@/lib/server/db';
|
|
145
|
+
import { UserEditor } from '@/components/UserEditor';
|
|
146
|
+
|
|
147
|
+
export default async function UserPage({ params }: { params: { id: string } }) {
|
|
148
|
+
const user = await db.user.findUnique({ where: { id: params.id } });
|
|
149
|
+
|
|
150
|
+
if (!user) return <NotFound />;
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div>
|
|
154
|
+
<h1>Edit User</h1>
|
|
155
|
+
<UserEditor user={user} /> {/* Client component for interactivity */}
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## API Patterns
|
|
162
|
+
|
|
163
|
+
### Server Actions (Full-Stack Frameworks)
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
// app/actions/users.ts
|
|
167
|
+
'use server';
|
|
168
|
+
|
|
169
|
+
import { db } from '@/lib/server/db';
|
|
170
|
+
import { revalidatePath } from 'next/cache';
|
|
171
|
+
|
|
172
|
+
export async function createUser(formData: FormData) {
|
|
173
|
+
const name = formData.get('name') as string;
|
|
174
|
+
const email = formData.get('email') as string;
|
|
175
|
+
|
|
176
|
+
await db.user.create({ data: { name, email } });
|
|
177
|
+
|
|
178
|
+
revalidatePath('/users');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Usage in component
|
|
182
|
+
<form action={createUser}>
|
|
183
|
+
<input name="name" />
|
|
184
|
+
<input name="email" type="email" />
|
|
185
|
+
<button type="submit">Create</button>
|
|
186
|
+
</form>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### API Routes
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
// app/api/users/route.ts
|
|
193
|
+
import { db } from '@/lib/server/db';
|
|
194
|
+
import { NextResponse } from 'next/server';
|
|
195
|
+
|
|
196
|
+
export async function GET() {
|
|
197
|
+
const users = await db.user.findMany();
|
|
198
|
+
return NextResponse.json({ data: users });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function POST(request: Request) {
|
|
202
|
+
const body = await request.json();
|
|
203
|
+
const user = await db.user.create({ data: body });
|
|
204
|
+
return NextResponse.json({ data: user }, { status: 201 });
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## State Synchronization
|
|
209
|
+
|
|
210
|
+
### Optimistic Updates
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
'use client';
|
|
214
|
+
|
|
215
|
+
import { useOptimistic } from 'react';
|
|
216
|
+
|
|
217
|
+
export function TodoList({ todos, addTodo }: TodoListProps) {
|
|
218
|
+
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
|
|
219
|
+
todos,
|
|
220
|
+
(state, newTodo: Todo) => [...state, newTodo]
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
async function handleAdd(formData: FormData) {
|
|
224
|
+
const title = formData.get('title') as string;
|
|
225
|
+
|
|
226
|
+
// Optimistically add to UI
|
|
227
|
+
addOptimisticTodo({ id: 'temp', title, completed: false });
|
|
228
|
+
|
|
229
|
+
// Then persist to server
|
|
230
|
+
await addTodo(formData);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<form action={handleAdd}>
|
|
235
|
+
<input name="title" />
|
|
236
|
+
<button type="submit">Add</button>
|
|
237
|
+
<ul>
|
|
238
|
+
{optimisticTodos.map(todo => (
|
|
239
|
+
<li key={todo.id}>{todo.title}</li>
|
|
240
|
+
))}
|
|
241
|
+
</ul>
|
|
242
|
+
</form>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Cache Invalidation
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
// After mutation, invalidate relevant caches
|
|
251
|
+
import { revalidatePath, revalidateTag } from 'next/cache';
|
|
252
|
+
|
|
253
|
+
export async function updateUser(id: string, data: UpdateUserInput) {
|
|
254
|
+
await db.user.update({ where: { id }, data });
|
|
255
|
+
|
|
256
|
+
// Invalidate specific path
|
|
257
|
+
revalidatePath(`/users/${id}`);
|
|
258
|
+
|
|
259
|
+
// Or invalidate by tag
|
|
260
|
+
revalidateTag('users');
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Error Handling Across Stack
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
// Server-side error
|
|
268
|
+
// app/api/users/[id]/route.ts
|
|
269
|
+
export async function GET(request: Request, { params }: { params: { id: string } }) {
|
|
270
|
+
const user = await db.user.findUnique({ where: { id: params.id } });
|
|
271
|
+
|
|
272
|
+
if (!user) {
|
|
273
|
+
return NextResponse.json(
|
|
274
|
+
{ error: { code: 'NOT_FOUND', message: 'User not found' } },
|
|
275
|
+
{ status: 404 }
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return NextResponse.json({ data: user });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Client-side handling
|
|
283
|
+
// components/UserProfile.tsx
|
|
284
|
+
'use client';
|
|
285
|
+
|
|
286
|
+
export function UserProfile({ userId }: { userId: string }) {
|
|
287
|
+
const { data, error, isLoading } = useQuery(['user', userId], () =>
|
|
288
|
+
fetch(`/api/users/${userId}`).then(res => {
|
|
289
|
+
if (!res.ok) throw new Error('Failed to fetch user');
|
|
290
|
+
return res.json();
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
if (isLoading) return <Skeleton />;
|
|
295
|
+
if (error) return <ErrorMessage error={error} />;
|
|
296
|
+
return <UserCard user={data.data} />;
|
|
297
|
+
}
|
|
298
|
+
```
|