@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,571 @@
|
|
|
1
|
+
# Language: Go
|
|
2
|
+
|
|
3
|
+
## Architecture: Clean Architecture with Go Idioms
|
|
4
|
+
|
|
5
|
+
**Organize Go applications with clear boundaries: Handlers → Use Cases → Domain → Infrastructure.**
|
|
6
|
+
|
|
7
|
+
### Project Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
project/
|
|
11
|
+
├── cmd/
|
|
12
|
+
│ └── server/
|
|
13
|
+
│ └── main.go # Application entry point
|
|
14
|
+
│
|
|
15
|
+
├── internal/
|
|
16
|
+
│ ├── handlers/ # HTTP handlers (presentation layer)
|
|
17
|
+
│ │ ├── user_handler.go
|
|
18
|
+
│ │ └── order_handler.go
|
|
19
|
+
│ │
|
|
20
|
+
│ ├── usecases/ # Business logic (application layer)
|
|
21
|
+
│ │ ├── user_usecase.go
|
|
22
|
+
│ │ └── order_usecase.go
|
|
23
|
+
│ │
|
|
24
|
+
│ ├── domain/ # Domain models and interfaces
|
|
25
|
+
│ │ ├── user.go # Domain entities
|
|
26
|
+
│ │ ├── order.go
|
|
27
|
+
│ │ └── repository.go # Repository interfaces
|
|
28
|
+
│ │
|
|
29
|
+
│ └── infrastructure/ # External concerns
|
|
30
|
+
│ ├── postgres/
|
|
31
|
+
│ │ └── user_repository.go
|
|
32
|
+
│ └── redis/
|
|
33
|
+
│ └── cache.go
|
|
34
|
+
│
|
|
35
|
+
└── pkg/ # Public, reusable packages
|
|
36
|
+
├── validator/
|
|
37
|
+
└── logger/
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Rule: Handlers Handle HTTP Only
|
|
41
|
+
|
|
42
|
+
**ALWAYS** keep HTTP handlers thin. They parse requests, call use cases, and serialize responses.
|
|
43
|
+
|
|
44
|
+
```go
|
|
45
|
+
// ✅ Good: Thin handler
|
|
46
|
+
package handlers
|
|
47
|
+
|
|
48
|
+
import (
|
|
49
|
+
"encoding/json"
|
|
50
|
+
"net/http"
|
|
51
|
+
"github.com/yourorg/project/internal/domain"
|
|
52
|
+
"github.com/yourorg/project/internal/usecases"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
type UserHandler struct {
|
|
56
|
+
userUseCase usecases.UserUseCase // Interface, not concrete type
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func NewUserHandler(uc usecases.UserUseCase) *UserHandler {
|
|
60
|
+
return &UserHandler{userUseCase: uc}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
|
64
|
+
// Parse request
|
|
65
|
+
var req CreateUserRequest
|
|
66
|
+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
67
|
+
http.Error(w, "Invalid request", http.StatusBadRequest)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Call use case (business logic)
|
|
72
|
+
user, err := h.userUseCase.CreateUser(r.Context(), req.Email, req.Name)
|
|
73
|
+
if err != nil {
|
|
74
|
+
if err == domain.ErrEmailExists {
|
|
75
|
+
http.Error(w, err.Error(), http.StatusConflict)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
http.Error(w, "Internal error", http.StatusInternalServerError)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Serialize response
|
|
83
|
+
w.Header().Set("Content-Type", "application/json")
|
|
84
|
+
w.WriteStatus(http.StatusCreated)
|
|
85
|
+
json.NewEncoder(w).Encode(toUserResponse(user))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ❌ Bad: Business logic in handler
|
|
89
|
+
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
|
90
|
+
var req CreateUserRequest
|
|
91
|
+
json.NewDecoder(r.Body).Decode(&req)
|
|
92
|
+
|
|
93
|
+
// ❌ Validation in handler
|
|
94
|
+
if !strings.Contains(req.Email, "@") {
|
|
95
|
+
http.Error(w, "Invalid email", http.StatusBadRequest)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ❌ Database access in handler
|
|
100
|
+
var existing User
|
|
101
|
+
h.db.QueryRow("SELECT * FROM users WHERE email = $1", req.Email).Scan(&existing)
|
|
102
|
+
if existing.ID != "" {
|
|
103
|
+
http.Error(w, "Email exists", http.StatusConflict)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ❌ Domain logic in handler
|
|
108
|
+
user := User{
|
|
109
|
+
ID: uuid.New().String(),
|
|
110
|
+
Email: req.Email,
|
|
111
|
+
Name: req.Name,
|
|
112
|
+
}
|
|
113
|
+
h.db.Exec("INSERT INTO users (id, email, name) VALUES ($1, $2, $3)",
|
|
114
|
+
user.ID, user.Email, user.Name)
|
|
115
|
+
|
|
116
|
+
json.NewEncoder(w).Encode(user)
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Rule: Use Cases Orchestrate Business Logic
|
|
121
|
+
|
|
122
|
+
**ALWAYS** put business logic in use cases. Use cases coordinate between repositories and domain models.
|
|
123
|
+
|
|
124
|
+
```go
|
|
125
|
+
// ✅ internal/usecases/user_usecase.go
|
|
126
|
+
package usecases
|
|
127
|
+
|
|
128
|
+
import (
|
|
129
|
+
"context"
|
|
130
|
+
"github.com/yourorg/project/internal/domain"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// UserUseCase interface (for dependency inversion)
|
|
134
|
+
type UserUseCase interface {
|
|
135
|
+
CreateUser(ctx context.Context, email, name string) (*domain.User, error)
|
|
136
|
+
UpdateUserEmail(ctx context.Context, userID, newEmail string) (*domain.User, error)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// userUseCase implementation
|
|
140
|
+
type userUseCase struct {
|
|
141
|
+
userRepo domain.UserRepository // Interface from domain layer
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func NewUserUseCase(userRepo domain.UserRepository) UserUseCase {
|
|
145
|
+
return &userUseCase{userRepo: userRepo}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
func (uc *userUseCase) CreateUser(ctx context.Context, email, name string) (*domain.User, error) {
|
|
149
|
+
// Validate using domain methods
|
|
150
|
+
if err := domain.ValidateEmail(email); err != nil {
|
|
151
|
+
return nil, err
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Business rule: Check uniqueness
|
|
155
|
+
existing, err := uc.userRepo.FindByEmail(ctx, email)
|
|
156
|
+
if err != nil {
|
|
157
|
+
return nil, err
|
|
158
|
+
}
|
|
159
|
+
if existing != nil {
|
|
160
|
+
return nil, domain.ErrEmailExists
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Create domain entity
|
|
164
|
+
user := domain.NewUser(email, name)
|
|
165
|
+
|
|
166
|
+
// Persist
|
|
167
|
+
if err := uc.userRepo.Save(ctx, user); err != nil {
|
|
168
|
+
return nil, err
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return user, nil
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
func (uc *userUseCase) UpdateUserEmail(ctx context.Context, userID, newEmail string) (*domain.User, error) {
|
|
175
|
+
user, err := uc.userRepo.FindByID(ctx, userID)
|
|
176
|
+
if err != nil {
|
|
177
|
+
return nil, err
|
|
178
|
+
}
|
|
179
|
+
if user == nil {
|
|
180
|
+
return nil, domain.ErrUserNotFound
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Use domain method (business logic)
|
|
184
|
+
if err := user.ChangeEmail(newEmail); err != nil {
|
|
185
|
+
return nil, err
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if err := uc.userRepo.Save(ctx, user); err != nil {
|
|
189
|
+
return nil, err
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return user, nil
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Rule: Domain Layer Contains Business Rules
|
|
197
|
+
|
|
198
|
+
**ALWAYS** put business rules and invariants in domain entities. Keep them infrastructure-agnostic.
|
|
199
|
+
|
|
200
|
+
```go
|
|
201
|
+
// ✅ internal/domain/user.go - Domain entity
|
|
202
|
+
package domain
|
|
203
|
+
|
|
204
|
+
import (
|
|
205
|
+
"errors"
|
|
206
|
+
"regexp"
|
|
207
|
+
"strings"
|
|
208
|
+
"time"
|
|
209
|
+
"github.com/google/uuid"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
// Domain errors (business errors)
|
|
213
|
+
var (
|
|
214
|
+
ErrInvalidEmail = errors.New("invalid email format")
|
|
215
|
+
ErrEmailExists = errors.New("email already exists")
|
|
216
|
+
ErrUserNotFound = errors.New("user not found")
|
|
217
|
+
ErrInvalidName = errors.New("name must be at least 2 characters")
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
// User is a domain entity
|
|
221
|
+
type User struct {
|
|
222
|
+
ID string
|
|
223
|
+
Email string
|
|
224
|
+
Name string
|
|
225
|
+
CreatedAt time.Time
|
|
226
|
+
IsActive bool
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// NewUser creates a new user with validation
|
|
230
|
+
func NewUser(email, name string) *User {
|
|
231
|
+
return &User{
|
|
232
|
+
ID: uuid.New().String(),
|
|
233
|
+
Email: email,
|
|
234
|
+
Name: name,
|
|
235
|
+
CreatedAt: time.Now().UTC(),
|
|
236
|
+
IsActive: true,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ChangeEmail changes user email with validation
|
|
241
|
+
func (u *User) ChangeEmail(newEmail string) error {
|
|
242
|
+
if err := ValidateEmail(newEmail); err != nil {
|
|
243
|
+
return err
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if strings.EqualFold(u.Email, newEmail) {
|
|
247
|
+
return errors.New("new email must be different")
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
u.Email = newEmail
|
|
251
|
+
return nil
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Deactivate deactivates the user account
|
|
255
|
+
func (u *User) Deactivate() error {
|
|
256
|
+
if !u.IsActive {
|
|
257
|
+
return errors.New("user is already inactive")
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
u.IsActive = false
|
|
261
|
+
return nil
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// IsAdmin checks if user is admin (business rule)
|
|
265
|
+
func (u *User) IsAdmin() bool {
|
|
266
|
+
return strings.HasSuffix(u.Email, "@company.com")
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ValidateEmail validates email format
|
|
270
|
+
func ValidateEmail(email string) error {
|
|
271
|
+
emailRegex := regexp.MustCompile(`^[^\s@]+@[^\s@]+\.[^\s@]+$`)
|
|
272
|
+
if !emailRegex.MatchString(email) {
|
|
273
|
+
return ErrInvalidEmail
|
|
274
|
+
}
|
|
275
|
+
if len(email) > 255 {
|
|
276
|
+
return errors.New("email too long")
|
|
277
|
+
}
|
|
278
|
+
return nil
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Rule: Repository Interfaces in Domain, Implementation in Infrastructure
|
|
283
|
+
|
|
284
|
+
**ALWAYS** define repository interfaces in domain layer. Implement them in infrastructure layer.
|
|
285
|
+
|
|
286
|
+
```go
|
|
287
|
+
// ✅ internal/domain/repository.go - Interface in domain
|
|
288
|
+
package domain
|
|
289
|
+
|
|
290
|
+
import "context"
|
|
291
|
+
|
|
292
|
+
// UserRepository defines the contract for user data access
|
|
293
|
+
type UserRepository interface {
|
|
294
|
+
Save(ctx context.Context, user *User) error
|
|
295
|
+
FindByID(ctx context.Context, id string) (*User, error)
|
|
296
|
+
FindByEmail(ctx context.Context, email string) (*User, error)
|
|
297
|
+
Delete(ctx context.Context, id string) error
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ✅ internal/infrastructure/postgres/user_repository.go - Implementation
|
|
301
|
+
package postgres
|
|
302
|
+
|
|
303
|
+
import (
|
|
304
|
+
"context"
|
|
305
|
+
"database/sql"
|
|
306
|
+
"errors"
|
|
307
|
+
"github.com/yourorg/project/internal/domain"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
type userRepository struct {
|
|
311
|
+
db *sql.DB
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// NewUserRepository creates a new PostgreSQL user repository
|
|
315
|
+
func NewUserRepository(db *sql.DB) domain.UserRepository {
|
|
316
|
+
return &userRepository{db: db}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
func (r *userRepository) Save(ctx context.Context, user *domain.User) error {
|
|
320
|
+
query := `
|
|
321
|
+
INSERT INTO users (id, email, name, created_at, is_active)
|
|
322
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
323
|
+
ON CONFLICT (id) DO UPDATE
|
|
324
|
+
SET email = $2, name = $3, is_active = $5
|
|
325
|
+
`
|
|
326
|
+
|
|
327
|
+
_, err := r.db.ExecContext(ctx, query,
|
|
328
|
+
user.ID,
|
|
329
|
+
user.Email,
|
|
330
|
+
user.Name,
|
|
331
|
+
user.CreatedAt,
|
|
332
|
+
user.IsActive,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return err
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
func (r *userRepository) FindByID(ctx context.Context, id string) (*domain.User, error) {
|
|
339
|
+
query := `SELECT id, email, name, created_at, is_active FROM users WHERE id = $1`
|
|
340
|
+
|
|
341
|
+
var user domain.User
|
|
342
|
+
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
|
343
|
+
&user.ID,
|
|
344
|
+
&user.Email,
|
|
345
|
+
&user.Name,
|
|
346
|
+
&user.CreatedAt,
|
|
347
|
+
&user.IsActive,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
if errors.Is(err, sql.ErrNoRows) {
|
|
351
|
+
return nil, nil // Not found
|
|
352
|
+
}
|
|
353
|
+
if err != nil {
|
|
354
|
+
return nil, err
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return &user, nil
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
func (r *userRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) {
|
|
361
|
+
query := `SELECT id, email, name, created_at, is_active FROM users WHERE email = $1`
|
|
362
|
+
|
|
363
|
+
var user domain.User
|
|
364
|
+
err := r.db.QueryRowContext(ctx, query, email).Scan(
|
|
365
|
+
&user.ID,
|
|
366
|
+
&user.Email,
|
|
367
|
+
&user.Name,
|
|
368
|
+
&user.CreatedAt,
|
|
369
|
+
&user.IsActive,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if errors.Is(err, sql.ErrNoRows) {
|
|
373
|
+
return nil, nil
|
|
374
|
+
}
|
|
375
|
+
if err != nil {
|
|
376
|
+
return nil, err
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return &user, nil
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
func (r *userRepository) Delete(ctx context.Context, id string) error {
|
|
383
|
+
query := `DELETE FROM users WHERE id = $1`
|
|
384
|
+
_, err := r.db.ExecContext(ctx, query, id)
|
|
385
|
+
return err
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Complete Example: Main Setup (Dependency Injection)
|
|
390
|
+
|
|
391
|
+
```go
|
|
392
|
+
// cmd/server/main.go - Wire dependencies
|
|
393
|
+
package main
|
|
394
|
+
|
|
395
|
+
import (
|
|
396
|
+
"database/sql"
|
|
397
|
+
"log"
|
|
398
|
+
"net/http"
|
|
399
|
+
|
|
400
|
+
_ "github.com/lib/pq"
|
|
401
|
+
"github.com/yourorg/project/internal/handlers"
|
|
402
|
+
"github.com/yourorg/project/internal/infrastructure/postgres"
|
|
403
|
+
"github.com/yourorg/project/internal/usecases"
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
func main() {
|
|
407
|
+
// Infrastructure: Database
|
|
408
|
+
db, err := sql.Open("postgres", "postgres://user:pass@localhost/db?sslmode=disable")
|
|
409
|
+
if err != nil {
|
|
410
|
+
log.Fatal(err)
|
|
411
|
+
}
|
|
412
|
+
defer db.Close()
|
|
413
|
+
|
|
414
|
+
// Infrastructure: Repositories (concrete implementations)
|
|
415
|
+
userRepo := postgres.NewUserRepository(db)
|
|
416
|
+
orderRepo := postgres.NewOrderRepository(db)
|
|
417
|
+
|
|
418
|
+
// Use Cases (business logic)
|
|
419
|
+
userUseCase := usecases.NewUserUseCase(userRepo)
|
|
420
|
+
orderUseCase := usecases.NewOrderUseCase(orderRepo, userRepo)
|
|
421
|
+
|
|
422
|
+
// Handlers (HTTP layer)
|
|
423
|
+
userHandler := handlers.NewUserHandler(userUseCase)
|
|
424
|
+
orderHandler := handlers.NewOrderHandler(orderUseCase)
|
|
425
|
+
|
|
426
|
+
// Routes
|
|
427
|
+
http.HandleFunc("/users", userHandler.CreateUser)
|
|
428
|
+
http.HandleFunc("/orders", orderHandler.CreateOrder)
|
|
429
|
+
|
|
430
|
+
log.Println("Server starting on :8080")
|
|
431
|
+
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Go Best Practices
|
|
436
|
+
|
|
437
|
+
- **ALWAYS** use `gofmt` to format code consistently.
|
|
438
|
+
- **ALWAYS** handle errors explicitly - never ignore them.
|
|
439
|
+
- **ALWAYS** use meaningful variable names (camelCase).
|
|
440
|
+
- **ALWAYS** write comprehensive godoc comments for exported functions/types.
|
|
441
|
+
- **ALWAYS** use `defer` for resource cleanup.
|
|
442
|
+
- **ALWAYS** prefer `struct` embedding over inheritance.
|
|
443
|
+
- **ALWAYS** use interfaces for abstraction and testing.
|
|
444
|
+
- **ALWAYS** use `context.Context` for cancellation and timeouts.
|
|
445
|
+
- **ALWAYS** use `go vet` and `golint` for code quality checks.
|
|
446
|
+
- **ALWAYS** write table-driven tests.
|
|
447
|
+
- **ALWAYS** use `t.Parallel()` for independent test functions.
|
|
448
|
+
- **ALWAYS** use `sync.WaitGroup` or channels for goroutine synchronization.
|
|
449
|
+
- **ALWAYS** avoid global variables - pass dependencies explicitly.
|
|
450
|
+
- **ALWAYS** use `panic` only for unrecoverable errors.
|
|
451
|
+
- **ALWAYS** use `log` package or structured logging for production.
|
|
452
|
+
- **ALWAYS** use `bufio` for efficient I/O operations.
|
|
453
|
+
- **ALWAYS** prefer `strings.Builder` for string concatenation in loops.
|
|
454
|
+
- **ALWAYS** use `time.Time` for time handling, not `int64`.
|
|
455
|
+
- **ALWAYS** use `json` tags for struct field serialization.
|
|
456
|
+
- **ALWAYS** use `iota` for enumerated constants.
|
|
457
|
+
- **ALWAYS** keep functions short and focused on single responsibilities.
|
|
458
|
+
|
|
459
|
+
## Testing Strategy
|
|
460
|
+
|
|
461
|
+
**Handlers:** Test HTTP contracts (mock use cases)
|
|
462
|
+
```go
|
|
463
|
+
// internal/handlers/user_handler_test.go
|
|
464
|
+
func TestCreateUser(t *testing.T) {
|
|
465
|
+
mockUseCase := &MockUserUseCase{
|
|
466
|
+
CreateUserFunc: func(ctx context.Context, email, name string) (*domain.User, error) {
|
|
467
|
+
return &domain.User{ID: "123", Email: email, Name: name}, nil
|
|
468
|
+
},
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
handler := NewUserHandler(mockUseCase)
|
|
472
|
+
|
|
473
|
+
req := httptest.NewRequest("POST", "/users", strings.NewReader(`{"email":"test@example.com","name":"Test"}`))
|
|
474
|
+
w := httptest.NewRecorder()
|
|
475
|
+
|
|
476
|
+
handler.CreateUser(w, req)
|
|
477
|
+
|
|
478
|
+
assert.Equal(t, http.StatusCreated, w.Code)
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Use Cases:** Test business logic (mock repositories)
|
|
483
|
+
```go
|
|
484
|
+
// internal/usecases/user_usecase_test.go
|
|
485
|
+
func TestCreateUser_EmailExists(t *testing.T) {
|
|
486
|
+
mockRepo := &MockUserRepository{
|
|
487
|
+
FindByEmailFunc: func(ctx context.Context, email string) (*domain.User, error) {
|
|
488
|
+
return &domain.User{ID: "existing"}, nil // Email exists
|
|
489
|
+
},
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
uc := NewUserUseCase(mockRepo)
|
|
493
|
+
|
|
494
|
+
_, err := uc.CreateUser(context.Background(), "test@example.com", "Test")
|
|
495
|
+
|
|
496
|
+
assert.Equal(t, domain.ErrEmailExists, err)
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Domain Models:** Test business rules (no mocking)
|
|
501
|
+
```go
|
|
502
|
+
// internal/domain/user_test.go
|
|
503
|
+
func TestUser_ChangeEmail(t *testing.T) {
|
|
504
|
+
tests := []struct {
|
|
505
|
+
name string
|
|
506
|
+
newEmail string
|
|
507
|
+
expectErr bool
|
|
508
|
+
}{
|
|
509
|
+
{"valid email", "new@example.com", false},
|
|
510
|
+
{"invalid email", "not-an-email", true},
|
|
511
|
+
{"same email", "test@example.com", true},
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
for _, tt := range tests {
|
|
515
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
516
|
+
user := NewUser("test@example.com", "Test")
|
|
517
|
+
err := user.ChangeEmail(tt.newEmail)
|
|
518
|
+
|
|
519
|
+
if tt.expectErr {
|
|
520
|
+
assert.Error(t, err)
|
|
521
|
+
} else {
|
|
522
|
+
assert.NoError(t, err)
|
|
523
|
+
assert.Equal(t, tt.newEmail, user.Email)
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**Repositories:** Integration tests with real database
|
|
531
|
+
```go
|
|
532
|
+
// internal/infrastructure/postgres/user_repository_test.go
|
|
533
|
+
func TestUserRepository_Save(t *testing.T) {
|
|
534
|
+
db := setupTestDB(t) // Helper to create test DB
|
|
535
|
+
defer db.Close()
|
|
536
|
+
|
|
537
|
+
repo := NewUserRepository(db)
|
|
538
|
+
user := domain.NewUser("test@example.com", "Test User")
|
|
539
|
+
|
|
540
|
+
err := repo.Save(context.Background(), user)
|
|
541
|
+
assert.NoError(t, err)
|
|
542
|
+
|
|
543
|
+
found, err := repo.FindByID(context.Background(), user.ID)
|
|
544
|
+
assert.NoError(t, err)
|
|
545
|
+
assert.Equal(t, user.Email, found.Email)
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## Architecture Summary
|
|
550
|
+
|
|
551
|
+
1. **Handlers** (`internal/handlers/`) = HTTP layer
|
|
552
|
+
- Parse requests, serialize responses
|
|
553
|
+
- Call use cases
|
|
554
|
+
- No business logic
|
|
555
|
+
|
|
556
|
+
2. **Use Cases** (`internal/usecases/`) = Business logic
|
|
557
|
+
- Orchestrate domain and repositories
|
|
558
|
+
- Application workflows
|
|
559
|
+
- Transaction boundaries
|
|
560
|
+
|
|
561
|
+
3. **Domain** (`internal/domain/`) = Business rules
|
|
562
|
+
- Entities with behavior
|
|
563
|
+
- Repository interfaces
|
|
564
|
+
- Framework-agnostic
|
|
565
|
+
|
|
566
|
+
4. **Infrastructure** (`internal/infrastructure/`) = External concerns
|
|
567
|
+
- Repository implementations
|
|
568
|
+
- Database access
|
|
569
|
+
- External APIs
|
|
570
|
+
|
|
571
|
+
**Reference:** See `patterns/architecture/clean-architecture.md`, `patterns/architecture/repository-pattern.md`, and `patterns/architecture/dependency-injection.md` for detailed architectural guidance.
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./BasePlugin-odQJAKA-.cjs"),n=require("./nodejs/index.cjs"),e=require("./go/index.cjs"),u=require("./python/index.cjs"),r=require("./rust/index.cjs"),P=require("./java/index.cjs"),g=require("./ruby/index.cjs"),l=require("./php/index.cjs"),t=require("./csharp/index.cjs"),o=require("./cpp/index.cjs"),s=require("./swift/index.cjs");exports.BasePlugin=i.BasePlugin;exports.NodeJSPlugin=n.NodeJSPlugin;exports.GoPlugin=e.GoPlugin;exports.PythonPlugin=u.PythonPlugin;exports.RustPlugin=r.RustPlugin;exports.JavaPlugin=P.JavaPlugin;exports.RubyPlugin=g.RubyPlugin;exports.PHPPlugin=l.PHPPlugin;exports.CSharpPlugin=t.CSharpPlugin;exports.CppPlugin=o.CppPlugin;exports.SwiftPlugin=s.SwiftPlugin;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { B as p } from "./BasePlugin-6wv0hYJ9.js";
|
|
2
|
+
import { NodeJSPlugin as P } from "./nodejs/index.mjs";
|
|
3
|
+
import { GoPlugin as u } from "./go/index.mjs";
|
|
4
|
+
import { PythonPlugin as i } from "./python/index.mjs";
|
|
5
|
+
import { RustPlugin as g } from "./rust/index.mjs";
|
|
6
|
+
import { JavaPlugin as m } from "./java/index.mjs";
|
|
7
|
+
import { RubyPlugin as a } from "./ruby/index.mjs";
|
|
8
|
+
import { PHPPlugin as S } from "./php/index.mjs";
|
|
9
|
+
import { CSharpPlugin as y } from "./csharp/index.mjs";
|
|
10
|
+
import { CppPlugin as C } from "./cpp/index.mjs";
|
|
11
|
+
import { SwiftPlugin as R } from "./swift/index.mjs";
|
|
12
|
+
export {
|
|
13
|
+
p as BasePlugin,
|
|
14
|
+
y as CSharpPlugin,
|
|
15
|
+
C as CppPlugin,
|
|
16
|
+
u as GoPlugin,
|
|
17
|
+
m as JavaPlugin,
|
|
18
|
+
P as NodeJSPlugin,
|
|
19
|
+
S as PHPPlugin,
|
|
20
|
+
i as PythonPlugin,
|
|
21
|
+
a as RubyPlugin,
|
|
22
|
+
g as RustPlugin,
|
|
23
|
+
R as SwiftPlugin
|
|
24
|
+
};
|
package/dist/java/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("node:path"),c=require("../BasePlugin-odQJAKA-.cjs");function d(n){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const t in n)if(t!=="default"){const a=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,a.get?a:{enumerable:!0,get:()=>n[t]})}}return e.default=n,Object.freeze(e)}const g=d(l);class m extends c.BasePlugin{constructor(){super(...arguments),this.name="java",this.displayName="Java",this.version="3.0.0",this.priority=75}async detect(e){return this.fileExists(e,"pom.xml")?this.detectMaven(e):this.fileExists(e,"build.gradle")||this.fileExists(e,"build.gradle.kts")?this.detectGradle(e):null}getTemplates(){return[{name:"java-core",tags:["java"],content:()=>this.loadTemplateFromFile(g.join(__dirname,"templates/lang-java.md")).then(e=>e||this.getJavaFallbackTemplate())}]}getJavaFallbackTemplate(){return`# Java Development Guidelines
|
|
2
2
|
|
|
3
3
|
This project uses Java.
|
|
4
4
|
|
|
@@ -20,4 +20,4 @@ This project uses Java.
|
|
|
20
20
|
## Testing
|
|
21
21
|
- Write JUnit tests
|
|
22
22
|
- Use Mockito for mocking
|
|
23
|
-
- Aim for good test coverage`}
|
|
23
|
+
- Aim for good test coverage`}getDependencyTagMap(){return{"org.springframework.boot:spring-boot":"spring-boot","spring-boot-starter":"spring-boot",junit:"junit"}}detectMaven(e){const t=this.readFile(e,"pom.xml");if(!t)return{language:"Java",name:this.getProjectName(e),dependencies:{},manifestFile:"pom.xml",projectPath:e};const a=t.match(/<artifactId>([^<]+)<\/artifactId>/),s=t.match(/<description>([^<]+)<\/description>/),r={},i=t.matchAll(/<dependency>[\s\S]*?<groupId>([^<]+)<\/groupId>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?(?:<version>([^<]+)<\/version>)?/g);for(const o of i)r[`${o[1]}:${o[2]}`]=o[3]||"*";return{language:"Java",name:a?.[1]||this.getProjectName(e),description:s?.[1],dependencies:r,manifestFile:"pom.xml",projectPath:e}}detectGradle(e){const t=this.fileExists(e,"build.gradle")?"build.gradle":"build.gradle.kts",a=this.readFile(e,t),s={};if(a){const r=a.matchAll(/(?:implementation|api|testImplementation)\s*['"]([^:'"]+):([^:'"]+):?([^'"]*)['"]/g);for(const i of r)s[`${i[1]}:${i[2]}`]=i[3]||"*"}return{language:"Java/Kotlin",name:this.getProjectName(e),dependencies:s,manifestFile:t,projectPath:e}}}exports.JavaPlugin=m;
|