@el-j/magic-helix-plugins 4.0.0-beta.2 → 4.0.0-beta.4
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/dist/architecture/codeowners.md +123 -0
- package/dist/architecture/monorepo.md +146 -0
- package/dist/architecture/nx.md +122 -0
- package/dist/architecture/turborepo.md +114 -0
- package/dist/ci/github-actions.md +268 -0
- package/dist/ci/gitlab-ci.md +330 -0
- package/dist/containers/docker-multistage.md +120 -0
- package/dist/containers/kubernetes-deploy.md +210 -0
- package/dist/cpp/index.cjs +79 -0
- package/dist/cpp/index.mjs +209 -0
- package/dist/csharp/index.cjs +2 -2
- package/dist/csharp/{index.js → index.mjs} +17 -11
- package/dist/csharp/templates/framework-aspnetcore.md +205 -0
- package/dist/csharp/templates/framework-blazor.md +271 -0
- package/dist/csharp/templates/lang-csharp.md +162 -0
- package/dist/devops/docker-compose.md +111 -0
- package/dist/devops/docker-dockerfile.md +94 -0
- package/dist/devops/github-actions.md +160 -0
- package/dist/devops/gitlab-ci.md +210 -0
- package/dist/generic/lang-typescript.md +57 -0
- package/dist/generic/state-redux.md +21 -0
- package/dist/generic/state-rxjs.md +6 -0
- package/dist/generic/style-mui.md +23 -0
- package/dist/generic/style-tailwind.md +76 -0
- package/dist/generic/test-cypress.md +21 -0
- package/dist/generic/test-jest.md +20 -0
- package/dist/generic/test-playwright.md +21 -0
- package/dist/generic/test-vitest.md +131 -0
- package/dist/go/index.cjs +3 -3
- package/dist/go/{index.js → index.mjs} +18 -15
- package/dist/go/templates/lang-go.md +571 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +24 -0
- package/dist/java/index.cjs +2 -2
- package/dist/java/{index.js → index.mjs} +25 -19
- package/dist/java/templates/build-gradle.md +102 -0
- package/dist/java/templates/build-maven.md +86 -0
- package/dist/java/templates/framework-spring-boot.md +179 -0
- package/dist/java/templates/lang-java.md +78 -0
- package/dist/java/templates/lang-kotlin.md +88 -0
- package/dist/meta/magic-helix-meta.md +213 -0
- package/dist/meta/meta-debug.md +459 -0
- package/dist/meta/meta-implement.md +450 -0
- package/dist/meta/meta-roadmap.md +265 -0
- package/dist/nodejs/templates/angular-core.md +19 -0
- package/dist/nodejs/templates/lang-typescript.md +57 -0
- package/dist/nodejs/templates/nestjs-core.md +7 -0
- package/dist/nodejs/templates/react-core.md +677 -0
- package/dist/nodejs/templates/react-zustand.md +7 -0
- package/dist/nodejs/templates/state-redux.md +21 -0
- package/dist/nodejs/templates/state-rxjs.md +6 -0
- package/dist/nodejs/templates/style-primevue.md +6 -0
- package/dist/nodejs/templates/style-quasar.md +22 -0
- package/dist/nodejs/templates/style-tailwind.md +76 -0
- package/dist/nodejs/templates/test-cypress.md +21 -0
- package/dist/nodejs/templates/test-jest.md +20 -0
- package/dist/nodejs/templates/test-playwright.md +21 -0
- package/dist/nodejs/templates/test-vitest.md +131 -0
- package/dist/nodejs/templates/vue-core.md +108 -0
- package/dist/nodejs/templates/vue-pinia.md +5 -0
- package/dist/patterns/architecture/clean-architecture.md +469 -0
- package/dist/patterns/architecture/dependency-injection.md +517 -0
- package/dist/patterns/architecture/domain-driven-design.md +621 -0
- package/dist/patterns/architecture/layered-architecture.md +382 -0
- package/dist/patterns/architecture/repository-pattern.md +408 -0
- package/dist/patterns/domain-expertise/nextjs-rules.md +115 -0
- package/dist/patterns/domain-expertise/react-patterns.md +181 -0
- package/dist/patterns/domain-expertise/server-components.md +212 -0
- package/dist/patterns/domain-expertise/shadcn-ui.md +52 -0
- package/dist/patterns/domain-expertise/tailwind-patterns.md +52 -0
- package/dist/patterns/environment/container-awareness.md +17 -0
- package/dist/patterns/environment/ide-features.md +17 -0
- package/dist/patterns/environment/os-commands.md +17 -0
- package/dist/patterns/organization/heading-hierarchy.md +103 -0
- package/dist/patterns/organization/sequential-workflows.md +102 -0
- package/dist/patterns/organization/xml-rule-groups.md +64 -0
- package/dist/patterns/reasoning/agent-loop.md +151 -0
- package/dist/patterns/reasoning/confirmation-gates.md +141 -0
- package/dist/patterns/reasoning/dependency-analysis.md +132 -0
- package/dist/patterns/reasoning/one-tool-per-iteration.md +152 -0
- package/dist/patterns/reasoning/preview-before-action.md +194 -0
- package/dist/patterns/reasoning/reflection-checkpoints.md +166 -0
- package/dist/patterns/reasoning/result-verification.md +157 -0
- package/dist/patterns/reasoning/subtask-breakdown.md +131 -0
- package/dist/patterns/reasoning/thinking-tags.md +100 -0
- package/dist/patterns/role-definition/capability-declarations.md +72 -0
- package/dist/patterns/role-definition/expert-identity.md +45 -0
- package/dist/patterns/role-definition/scope-boundaries.md +61 -0
- package/dist/patterns/safety/code-safety-rules.md +17 -0
- package/dist/patterns/safety/credential-handling.md +17 -0
- package/dist/patterns/safety/destructive-warnings.md +17 -0
- package/dist/patterns/safety/refusal-messages.md +17 -0
- package/dist/patterns/tone/adaptive-tone.md +17 -0
- package/dist/patterns/tone/concise-communication.md +17 -0
- package/dist/patterns/tone/forbidden-phrases.md +17 -0
- package/dist/patterns/tool-guidelines/function-schemas.md +143 -0
- package/dist/patterns/tool-guidelines/parameter-examples.md +137 -0
- package/dist/patterns/tool-guidelines/usage-policies.md +105 -0
- package/dist/php/index.cjs +2 -2
- package/dist/php/{index.js → index.mjs} +12 -6
- package/dist/php/templates/framework-laravel.md +112 -0
- package/dist/php/templates/lang-php.md +94 -0
- package/dist/python/index.cjs +4 -4
- package/dist/python/{index.js → index.mjs} +10 -7
- package/dist/python/templates/lang-python.md +508 -0
- package/dist/ruby/index.cjs +2 -2
- package/dist/ruby/{index.js → index.mjs} +16 -10
- package/dist/ruby/templates/framework-rails.md +309 -0
- package/dist/ruby/templates/framework-sinatra.md +227 -0
- package/dist/ruby/templates/lang-ruby.md +216 -0
- package/dist/rust/index.cjs +3 -3
- package/dist/rust/{index.js → index.mjs} +24 -18
- package/dist/rust/templates/lang-rust.md +89 -0
- package/dist/swift/index.cjs +32 -0
- package/dist/swift/index.mjs +112 -0
- package/dist/swift/templates/framework-vapor.md +352 -0
- package/dist/swift/templates/lang-swift.md +291 -0
- package/package.json +31 -21
- package/dist/index.js +0 -20
- /package/dist/nodejs/{index.js → index.mjs} +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Repository Pattern
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Abstract data access logic, providing a collection-like interface for domain objects. Decouples business logic from data persistence.
|
|
5
|
+
|
|
6
|
+
## Core Concept
|
|
7
|
+
|
|
8
|
+
**Repository = Collection of Domain Objects**
|
|
9
|
+
|
|
10
|
+
Think of a repository as an in-memory collection (like a List or Set) that persists to a database.
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
// ✅ Repository interface acts like a collection
|
|
14
|
+
interface IProductRepository {
|
|
15
|
+
// Collection-like operations
|
|
16
|
+
getById(id: string): Promise<Product | null>;
|
|
17
|
+
getAll(): Promise<Product[]>;
|
|
18
|
+
add(product: Product): Promise<void>;
|
|
19
|
+
update(product: Product): Promise<void>;
|
|
20
|
+
remove(id: string): Promise<void>;
|
|
21
|
+
|
|
22
|
+
// Domain-specific queries
|
|
23
|
+
findByCategory(category: string): Promise<Product[]>;
|
|
24
|
+
findInPriceRange(min: number, max: number): Promise<Product[]>;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Implementation
|
|
29
|
+
|
|
30
|
+
### 1. Define Repository Interface (in domain/use case layer)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// ✅ Interface lives in domain layer (no implementation details)
|
|
34
|
+
export interface IUserRepository {
|
|
35
|
+
findById(id: string): Promise<User | null>;
|
|
36
|
+
findByEmail(email: string): Promise<User | null>;
|
|
37
|
+
findAll(filters?: UserFilters): Promise<User[]>;
|
|
38
|
+
save(user: User): Promise<void>;
|
|
39
|
+
delete(id: string): Promise<void>;
|
|
40
|
+
|
|
41
|
+
// Domain-specific queries
|
|
42
|
+
findActiveUsers(): Promise<User[]>;
|
|
43
|
+
findUsersByRole(role: string): Promise<User[]>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UserFilters {
|
|
47
|
+
role?: string;
|
|
48
|
+
isActive?: boolean;
|
|
49
|
+
registeredAfter?: Date;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Implement Repository (in infrastructure layer)
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// ✅ Implementation with actual database
|
|
57
|
+
export class PostgresUserRepository implements IUserRepository {
|
|
58
|
+
constructor(private db: Database) {}
|
|
59
|
+
|
|
60
|
+
async findById(id: string): Promise<User | null> {
|
|
61
|
+
const row = await this.db.queryOne(
|
|
62
|
+
'SELECT * FROM users WHERE id = $1',
|
|
63
|
+
[id]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return row ? this.toDomain(row) : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
70
|
+
const row = await this.db.queryOne(
|
|
71
|
+
'SELECT * FROM users WHERE email = $1',
|
|
72
|
+
[email]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return row ? this.toDomain(row) : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async save(user: User): Promise<void> {
|
|
79
|
+
// Handle both insert and update (upsert)
|
|
80
|
+
await this.db.query(
|
|
81
|
+
`INSERT INTO users (id, email, password, role, created_at)
|
|
82
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
83
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
84
|
+
email = $2,
|
|
85
|
+
password = $3,
|
|
86
|
+
role = $4`,
|
|
87
|
+
[user.id, user.email, user.password, user.role, user.createdAt]
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async delete(id: string): Promise<void> {
|
|
92
|
+
await this.db.query('DELETE FROM users WHERE id = $1', [id]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async findActiveUsers(): Promise<User[]> {
|
|
96
|
+
const rows = await this.db.query(
|
|
97
|
+
'SELECT * FROM users WHERE is_active = true ORDER BY created_at DESC'
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return rows.map(row => this.toDomain(row));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async findUsersByRole(role: string): Promise<User[]> {
|
|
104
|
+
const rows = await this.db.query(
|
|
105
|
+
'SELECT * FROM users WHERE role = $1',
|
|
106
|
+
[role]
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return rows.map(row => this.toDomain(row));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Map database row to domain object
|
|
113
|
+
private toDomain(row: any): User {
|
|
114
|
+
return new User(
|
|
115
|
+
row.id,
|
|
116
|
+
row.email,
|
|
117
|
+
row.password,
|
|
118
|
+
row.role,
|
|
119
|
+
row.created_at,
|
|
120
|
+
row.is_active
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Map domain object to database row
|
|
125
|
+
private toRow(user: User): any {
|
|
126
|
+
return {
|
|
127
|
+
id: user.id,
|
|
128
|
+
email: user.email,
|
|
129
|
+
password: user.password,
|
|
130
|
+
role: user.role,
|
|
131
|
+
created_at: user.createdAt,
|
|
132
|
+
is_active: user.isActive,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 3. Use Repository in Service/Use Case
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// ✅ Service depends on interface, not implementation
|
|
142
|
+
export class UserService {
|
|
143
|
+
constructor(private userRepository: IUserRepository) {}
|
|
144
|
+
|
|
145
|
+
async registerUser(email: string, password: string): Promise<User> {
|
|
146
|
+
// Check if user exists
|
|
147
|
+
const existing = await this.userRepository.findByEmail(email);
|
|
148
|
+
if (existing) {
|
|
149
|
+
throw new Error('User already exists');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Create domain object
|
|
153
|
+
const user = User.create(email, password);
|
|
154
|
+
|
|
155
|
+
// Persist
|
|
156
|
+
await this.userRepository.save(user);
|
|
157
|
+
|
|
158
|
+
return user;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async getActiveAdmins(): Promise<User[]> {
|
|
162
|
+
const admins = await this.userRepository.findUsersByRole('admin');
|
|
163
|
+
return admins.filter(user => user.isActive);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Multiple Implementations
|
|
169
|
+
|
|
170
|
+
### In-Memory Repository (for testing)
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// ✅ In-memory implementation for fast tests
|
|
174
|
+
export class InMemoryUserRepository implements IUserRepository {
|
|
175
|
+
private users: Map<string, User> = new Map();
|
|
176
|
+
|
|
177
|
+
async findById(id: string): Promise<User | null> {
|
|
178
|
+
return this.users.get(id) || null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
182
|
+
return Array.from(this.users.values())
|
|
183
|
+
.find(user => user.email === email) || null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async save(user: User): Promise<void> {
|
|
187
|
+
this.users.set(user.id, user);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async delete(id: string): Promise<void> {
|
|
191
|
+
this.users.delete(id);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async findActiveUsers(): Promise<User[]> {
|
|
195
|
+
return Array.from(this.users.values())
|
|
196
|
+
.filter(user => user.isActive);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async findUsersByRole(role: string): Promise<User[]> {
|
|
200
|
+
return Array.from(this.users.values())
|
|
201
|
+
.filter(user => user.role === role);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Test helper
|
|
205
|
+
clear(): void {
|
|
206
|
+
this.users.clear();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Redis Repository (for caching)
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// ✅ Redis implementation for caching layer
|
|
215
|
+
export class RedisUserRepository implements IUserRepository {
|
|
216
|
+
constructor(
|
|
217
|
+
private redis: Redis,
|
|
218
|
+
private fallbackRepo: IUserRepository // Delegate to DB if cache miss
|
|
219
|
+
) {}
|
|
220
|
+
|
|
221
|
+
async findById(id: string): Promise<User | null> {
|
|
222
|
+
// Try cache first
|
|
223
|
+
const cached = await this.redis.get(`user:${id}`);
|
|
224
|
+
if (cached) {
|
|
225
|
+
return JSON.parse(cached);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Fall back to database
|
|
229
|
+
const user = await this.fallbackRepo.findById(id);
|
|
230
|
+
if (user) {
|
|
231
|
+
await this.redis.setex(`user:${id}`, 3600, JSON.stringify(user));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return user;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async save(user: User): Promise<void> {
|
|
238
|
+
// Save to database
|
|
239
|
+
await this.fallbackRepo.save(user);
|
|
240
|
+
|
|
241
|
+
// Update cache
|
|
242
|
+
await this.redis.setex(`user:${user.id}`, 3600, JSON.stringify(user));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async delete(id: string): Promise<void> {
|
|
246
|
+
await this.fallbackRepo.delete(id);
|
|
247
|
+
await this.redis.del(`user:${id}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Other methods...
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Specification Pattern (Advanced)
|
|
255
|
+
|
|
256
|
+
For complex queries, use specification pattern:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// ✅ Specification for complex filtering
|
|
260
|
+
export interface Specification<T> {
|
|
261
|
+
isSatisfiedBy(item: T): boolean;
|
|
262
|
+
toSql(): { where: string; params: any[] }; // Optional for DB queries
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export class ActiveUserSpec implements Specification<User> {
|
|
266
|
+
isSatisfiedBy(user: User): boolean {
|
|
267
|
+
return user.isActive;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
toSql() {
|
|
271
|
+
return { where: 'is_active = true', params: [] };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export class RoleSpec implements Specification<User> {
|
|
276
|
+
constructor(private role: string) {}
|
|
277
|
+
|
|
278
|
+
isSatisfiedBy(user: User): boolean {
|
|
279
|
+
return user.role === this.role;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
toSql() {
|
|
283
|
+
return { where: 'role = $1', params: [this.role] };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ✅ Combine specifications
|
|
288
|
+
export class AndSpec<T> implements Specification<T> {
|
|
289
|
+
constructor(private specs: Specification<T>[]) {}
|
|
290
|
+
|
|
291
|
+
isSatisfiedBy(item: T): boolean {
|
|
292
|
+
return this.specs.every(spec => spec.isSatisfiedBy(item));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
toSql() {
|
|
296
|
+
const sqlParts = this.specs.map(s => s.toSql());
|
|
297
|
+
const where = sqlParts.map((s, i) => `(${s.where})`).join(' AND ');
|
|
298
|
+
const params = sqlParts.flatMap(s => s.params);
|
|
299
|
+
return { where, params };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ✅ Repository accepts specifications
|
|
304
|
+
interface IUserRepository {
|
|
305
|
+
find(spec: Specification<User>): Promise<User[]>;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Usage
|
|
309
|
+
const activeAdmins = await userRepo.find(
|
|
310
|
+
new AndSpec([
|
|
311
|
+
new ActiveUserSpec(),
|
|
312
|
+
new RoleSpec('admin')
|
|
313
|
+
])
|
|
314
|
+
);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Testing Strategy
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// ✅ Test service with in-memory repository
|
|
321
|
+
describe('UserService', () => {
|
|
322
|
+
let service: UserService;
|
|
323
|
+
let repo: InMemoryUserRepository;
|
|
324
|
+
|
|
325
|
+
beforeEach(() => {
|
|
326
|
+
repo = new InMemoryUserRepository();
|
|
327
|
+
service = new UserService(repo);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('registers new user', async () => {
|
|
331
|
+
const user = await service.registerUser('test@example.com', 'password');
|
|
332
|
+
|
|
333
|
+
expect(user.email).toBe('test@example.com');
|
|
334
|
+
|
|
335
|
+
// Verify persisted
|
|
336
|
+
const found = await repo.findById(user.id);
|
|
337
|
+
expect(found).toBeTruthy();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('throws if user already exists', async () => {
|
|
341
|
+
await service.registerUser('test@example.com', 'password');
|
|
342
|
+
|
|
343
|
+
await expect(
|
|
344
|
+
service.registerUser('test@example.com', 'password')
|
|
345
|
+
).rejects.toThrow('User already exists');
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// ✅ Test repository with real database (integration test)
|
|
350
|
+
describe('PostgresUserRepository', () => {
|
|
351
|
+
let repo: PostgresUserRepository;
|
|
352
|
+
let db: Database;
|
|
353
|
+
|
|
354
|
+
beforeEach(async () => {
|
|
355
|
+
db = await createTestDatabase();
|
|
356
|
+
repo = new PostgresUserRepository(db);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
afterEach(async () => {
|
|
360
|
+
await db.close();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('saves and retrieves user', async () => {
|
|
364
|
+
const user = new User('1', 'test@example.com', 'hash', 'user');
|
|
365
|
+
|
|
366
|
+
await repo.save(user);
|
|
367
|
+
const found = await repo.findById('1');
|
|
368
|
+
|
|
369
|
+
expect(found?.email).toBe('test@example.com');
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## File Organization
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
src/
|
|
378
|
+
├── domain/
|
|
379
|
+
│ ├── models/
|
|
380
|
+
│ │ └── User.ts
|
|
381
|
+
│ └── repositories/ # Interfaces only
|
|
382
|
+
│ ├── IUserRepository.ts
|
|
383
|
+
│ └── IOrderRepository.ts
|
|
384
|
+
│
|
|
385
|
+
├── application/
|
|
386
|
+
│ └── services/
|
|
387
|
+
│ └── UserService.ts # Uses IUserRepository
|
|
388
|
+
│
|
|
389
|
+
└── infrastructure/
|
|
390
|
+
└── repositories/ # Implementations
|
|
391
|
+
├── PostgresUserRepository.ts
|
|
392
|
+
├── InMemoryUserRepository.ts
|
|
393
|
+
└── RedisUserRepository.ts
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Rules Summary
|
|
397
|
+
|
|
398
|
+
- **ALWAYS** define repository interfaces in domain layer
|
|
399
|
+
- **ALWAYS** implement repositories in infrastructure layer
|
|
400
|
+
- **ALWAYS** use collection-like method names (add, remove, getById)
|
|
401
|
+
- **ALWAYS** return domain objects, not database rows
|
|
402
|
+
- **ALWAYS** make repositories responsible for data mapping only
|
|
403
|
+
- **NEVER** put business logic in repositories
|
|
404
|
+
- **NEVER** return database-specific types (like ORM entities)
|
|
405
|
+
- **PREFER** one repository per aggregate root
|
|
406
|
+
- **PREFER** repository methods that work with domain objects, not primitive types
|
|
407
|
+
- **CONSIDER** using in-memory implementation for testing
|
|
408
|
+
- **CONSIDER** specification pattern for complex queries
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Next.js Rules Pattern
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Enforce Next.js-specific best practices and constraints. From **v0** pattern.
|
|
5
|
+
|
|
6
|
+
## Template
|
|
7
|
+
|
|
8
|
+
```markdown
|
|
9
|
+
## Next.js {VERSION} Guidelines
|
|
10
|
+
|
|
11
|
+
### App Router (Next.js 13+)
|
|
12
|
+
- {RULE_ABOUT_SERVER_COMPONENTS}
|
|
13
|
+
- {RULE_ABOUT_CLIENT_COMPONENTS}
|
|
14
|
+
- {RULE_ABOUT_FILE_CONVENTIONS}
|
|
15
|
+
- {RULE_ABOUT_ROUTING}
|
|
16
|
+
|
|
17
|
+
### Data Fetching
|
|
18
|
+
- {RULE_ABOUT_ASYNC_COMPONENTS}
|
|
19
|
+
- {RULE_ABOUT_CACHING}
|
|
20
|
+
- {RULE_ABOUT_REVALIDATION}
|
|
21
|
+
|
|
22
|
+
### Performance
|
|
23
|
+
- {RULE_ABOUT_IMAGES}
|
|
24
|
+
- {RULE_ABOUT_FONTS}
|
|
25
|
+
- {RULE_ABOUT_BUNDLING}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
### v0 (Next.js 14 App Router)
|
|
31
|
+
```markdown
|
|
32
|
+
## Next.js 14 App Router Guidelines
|
|
33
|
+
|
|
34
|
+
### Server vs Client Components
|
|
35
|
+
- **Default to Server Components**: All components in app/ are Server Components unless marked with 'use client'
|
|
36
|
+
- **Use 'use client' only when**:
|
|
37
|
+
- Component uses React hooks (useState, useEffect, useContext)
|
|
38
|
+
- Component uses browser APIs (window, document, localStorage)
|
|
39
|
+
- Component needs event handlers (onClick, onSubmit, etc.)
|
|
40
|
+
- Component uses third-party libraries that depend on browser features
|
|
41
|
+
|
|
42
|
+
**Example**:
|
|
43
|
+
```typescript
|
|
44
|
+
// ✅ Server Component (default) - good for static content
|
|
45
|
+
export default function ProductList({ products }: Props) {
|
|
46
|
+
return <div>{products.map(p => <ProductCard key={p.id} {...p} />)}</div>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ✅ Client Component - needed for interactivity
|
|
50
|
+
'use client';
|
|
51
|
+
export default function SearchBar() {
|
|
52
|
+
const [query, setQuery] = useState('');
|
|
53
|
+
return <input value={query} onChange={e => setQuery(e.target.value)} />;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### File Conventions
|
|
58
|
+
- **page.tsx**: Route segment UI (exported as default)
|
|
59
|
+
- **layout.tsx**: Shared UI across route segments
|
|
60
|
+
- **loading.tsx**: Loading UI (Suspense boundary)
|
|
61
|
+
- **error.tsx**: Error UI (Error boundary)
|
|
62
|
+
- **not-found.tsx**: 404 UI
|
|
63
|
+
- **route.ts**: API endpoint (GET, POST, etc.)
|
|
64
|
+
|
|
65
|
+
**Route Groups**: Use (folder) to organize without affecting URL structure
|
|
66
|
+
|
|
67
|
+
### Data Fetching
|
|
68
|
+
- **Async Server Components**: Fetch data directly in component
|
|
69
|
+
```typescript
|
|
70
|
+
export default async function Page() {
|
|
71
|
+
const data = await fetch('https://api.example.com/data').then(r => r.json());
|
|
72
|
+
return <div>{data.title}</div>;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
- **Caching**: Fetch requests are automatically cached
|
|
76
|
+
- **Revalidation**: Use `revalidate` or `revalidatePath` for on-demand updates
|
|
77
|
+
|
|
78
|
+
### Performance
|
|
79
|
+
- **Image Optimization**: Always use `next/image` component
|
|
80
|
+
```typescript
|
|
81
|
+
import Image from 'next/image';
|
|
82
|
+
<Image src="/photo.jpg" width={500} height={300} alt="Photo" />
|
|
83
|
+
```
|
|
84
|
+
- **Font Optimization**: Use `next/font` for self-hosted fonts
|
|
85
|
+
```typescript
|
|
86
|
+
import { Inter } from 'next/font/google';
|
|
87
|
+
const inter = Inter({ subsets: ['latin'] });
|
|
88
|
+
```
|
|
89
|
+
- **Dynamic Imports**: Code-split heavy components
|
|
90
|
+
```typescript
|
|
91
|
+
const HeavyComponent = dynamic(() => import('./HeavyComponent'), { ssr: false });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Metadata
|
|
95
|
+
- Export `metadata` object or `generateMetadata` function for SEO
|
|
96
|
+
```typescript
|
|
97
|
+
export const metadata = {
|
|
98
|
+
title: 'Page Title',
|
|
99
|
+
description: 'Page description',
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Variables
|
|
105
|
+
- `{VERSION}`: Next.js version (13, 14, 15)
|
|
106
|
+
- `{RULE_ABOUT_X}`: Specific guideline for feature X
|
|
107
|
+
|
|
108
|
+
## Best Practices
|
|
109
|
+
1. Specify Next.js version (APIs change between versions)
|
|
110
|
+
2. Distinguish Pages Router vs App Router
|
|
111
|
+
3. Highlight breaking changes from previous versions
|
|
112
|
+
4. Show code examples (good ✅ vs bad ❌)
|
|
113
|
+
5. Link to official docs for deep dives
|
|
114
|
+
6. Update when new Next.js versions release
|
|
115
|
+
7. Benefits: Version-appropriate code, avoids deprecated patterns, follows framework conventions
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# React Patterns Pattern
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Enforce React best practices and common patterns. From **v0** and **same.new**.
|
|
5
|
+
|
|
6
|
+
## Template
|
|
7
|
+
|
|
8
|
+
```markdown
|
|
9
|
+
## React Best Practices
|
|
10
|
+
|
|
11
|
+
### Component Structure
|
|
12
|
+
- {RULE_ABOUT_FUNCTIONAL_VS_CLASS}
|
|
13
|
+
- {RULE_ABOUT_HOOKS_USAGE}
|
|
14
|
+
- {RULE_ABOUT_COMPOSITION}
|
|
15
|
+
|
|
16
|
+
### State Management
|
|
17
|
+
- {RULE_ABOUT_LOCAL_STATE}
|
|
18
|
+
- {RULE_ABOUT_PROP_DRILLING}
|
|
19
|
+
- {RULE_ABOUT_CONTEXT}
|
|
20
|
+
|
|
21
|
+
### Performance
|
|
22
|
+
- {RULE_ABOUT_MEMOIZATION}
|
|
23
|
+
- {RULE_ABOUT_KEYS}
|
|
24
|
+
- {RULE_ABOUT_EFFECTS}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
### v0 (React 18+ Patterns)
|
|
30
|
+
```markdown
|
|
31
|
+
## React 18+ Best Practices
|
|
32
|
+
|
|
33
|
+
### Component Structure
|
|
34
|
+
- **Use Functional Components**: No class components (deprecated pattern)
|
|
35
|
+
```typescript
|
|
36
|
+
// ✅ Functional component with TypeScript
|
|
37
|
+
interface ButtonProps {
|
|
38
|
+
label: string;
|
|
39
|
+
onClick: () => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
|
|
43
|
+
return <button onClick={onClick}>{label}</button>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ❌ Class component (outdated)
|
|
47
|
+
class Button extends React.Component { /* ... */ }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- **Extract Custom Hooks**: Reuse stateful logic
|
|
51
|
+
```typescript
|
|
52
|
+
// ✅ Custom hook for form state
|
|
53
|
+
function useFormField(initialValue: string) {
|
|
54
|
+
const [value, setValue] = useState(initialValue);
|
|
55
|
+
const [error, setError] = useState<string | null>(null);
|
|
56
|
+
|
|
57
|
+
const validate = () => {
|
|
58
|
+
if (!value) setError('Required');
|
|
59
|
+
else setError(null);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return { value, setValue, error, validate };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Usage in component
|
|
66
|
+
function SignupForm() {
|
|
67
|
+
const email = useFormField('');
|
|
68
|
+
const password = useFormField('');
|
|
69
|
+
// ...
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **Prefer Composition over Inheritance**
|
|
74
|
+
```typescript
|
|
75
|
+
// ✅ Composition with children prop
|
|
76
|
+
function Card({ children }: { children: React.ReactNode }) {
|
|
77
|
+
return <div className="card">{children}</div>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ProductCard() {
|
|
81
|
+
return (
|
|
82
|
+
<Card>
|
|
83
|
+
<h2>Product Name</h2>
|
|
84
|
+
<p>Description</p>
|
|
85
|
+
</Card>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### State Management
|
|
91
|
+
- **Start with Local State**: Use useState for component-specific state
|
|
92
|
+
- **Lift State Up**: Move state to common parent when multiple children need it
|
|
93
|
+
- **Use Context for Global State**: Avoid prop drilling for deeply nested components
|
|
94
|
+
```typescript
|
|
95
|
+
// ✅ Context for theme (accessed by many components)
|
|
96
|
+
const ThemeContext = createContext<'light' | 'dark'>('light');
|
|
97
|
+
|
|
98
|
+
function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
99
|
+
const [theme, setTheme] = useState<'light' | 'dark'>('light');
|
|
100
|
+
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- **Consider Zustand/Jotai**: For complex client-side state (alternative to Redux)
|
|
105
|
+
|
|
106
|
+
### Performance
|
|
107
|
+
- **Memoize Expensive Calculations**: Use useMemo
|
|
108
|
+
```typescript
|
|
109
|
+
const sortedItems = useMemo(() =>
|
|
110
|
+
items.sort((a, b) => a.price - b.price),
|
|
111
|
+
[items]
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **Memoize Callbacks**: Use useCallback for props passed to children
|
|
116
|
+
```typescript
|
|
117
|
+
const handleClick = useCallback(() => {
|
|
118
|
+
console.log('Clicked');
|
|
119
|
+
}, []); // Empty deps = never recreated
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- **Always Provide Keys**: For lists (use stable IDs, not indexes)
|
|
123
|
+
```typescript
|
|
124
|
+
// ✅ Stable unique key
|
|
125
|
+
{products.map(p => <ProductCard key={p.id} {...p} />)}
|
|
126
|
+
|
|
127
|
+
// ❌ Index as key (causes bugs when list changes)
|
|
128
|
+
{products.map((p, i) => <ProductCard key={i} {...p} />)}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- **Avoid Side Effects in Render**: Use useEffect for side effects
|
|
132
|
+
```typescript
|
|
133
|
+
// ❌ Side effect in render (runs every render)
|
|
134
|
+
function Component() {
|
|
135
|
+
document.title = 'New Title'; // Bad!
|
|
136
|
+
return <div>Content</div>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ✅ Side effect in useEffect (runs only when needed)
|
|
140
|
+
function Component() {
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
document.title = 'New Title';
|
|
143
|
+
}, []);
|
|
144
|
+
return <div>Content</div>;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### TypeScript Integration
|
|
149
|
+
- **Define Prop Interfaces**: Explicit types for all components
|
|
150
|
+
- **Use React.FC sparingly**: Prefer explicit return types
|
|
151
|
+
```typescript
|
|
152
|
+
// ✅ Explicit props and return type
|
|
153
|
+
interface Props {
|
|
154
|
+
name: string;
|
|
155
|
+
age: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function UserCard({ name, age }: Props): JSX.Element {
|
|
159
|
+
return <div>{name}, {age}</div>;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- **Type Event Handlers**: Use React's event types
|
|
164
|
+
```typescript
|
|
165
|
+
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
166
|
+
console.log(e.target.value);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Variables
|
|
172
|
+
- `{RULE_ABOUT_X}`: Specific React guideline
|
|
173
|
+
|
|
174
|
+
## Best Practices
|
|
175
|
+
1. Cover React fundamentals (components, hooks, effects)
|
|
176
|
+
2. Show TypeScript integration examples
|
|
177
|
+
3. Include good/bad code comparisons
|
|
178
|
+
4. Address common mistakes (useEffect dependencies, key props)
|
|
179
|
+
5. Mention state management options (Context, Zustand, Redux)
|
|
180
|
+
6. Update for React version features (18+: Suspense, Concurrent Rendering)
|
|
181
|
+
7. Benefits: Consistent React patterns, avoids common pitfalls, TypeScript-safe code
|