@el-j/magic-helix-core 4.0.0-beta.1 → 4.0.0-beta.3

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.
Files changed (160) hide show
  1. package/dist/index-B88j4AyE.js +13 -0
  2. package/dist/index-B88j4AyE.js.map +1 -0
  3. package/dist/index-CY-pQbuu.cjs +2 -0
  4. package/dist/index-CY-pQbuu.cjs.map +1 -0
  5. package/dist/index.cjs +75 -1
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.mjs +2234 -51
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/pattern-combiner.d.ts +1 -1
  11. package/dist/plugin-loader.d.ts +7 -1
  12. package/package.json +4 -4
  13. package/dist/BasePlugin-6wv0hYJ9.js +0 -98
  14. package/dist/BasePlugin-6wv0hYJ9.js.map +0 -1
  15. package/dist/BasePlugin-odQJAKA-.cjs +0 -2
  16. package/dist/BasePlugin-odQJAKA-.cjs.map +0 -1
  17. package/dist/builtin-plugins/base/BasePlugin.d.ts +0 -69
  18. package/dist/builtin-plugins/csharp/index.d.ts +0 -20
  19. package/dist/builtin-plugins/go/index.d.ts +0 -23
  20. package/dist/builtin-plugins/index.d.ts +0 -15
  21. package/dist/builtin-plugins/java/index.d.ts +0 -22
  22. package/dist/builtin-plugins/nodejs/index.d.ts +0 -44
  23. package/dist/builtin-plugins/php/index.d.ts +0 -20
  24. package/dist/builtin-plugins/python/index.d.ts +0 -27
  25. package/dist/builtin-plugins/ruby/index.d.ts +0 -20
  26. package/dist/builtin-plugins/rust/index.d.ts +0 -53
  27. package/dist/builtin-plugins/swift/index.d.ts +0 -22
  28. package/dist/default_templates/angular/angular-core.md +0 -19
  29. package/dist/default_templates/architecture/codeowners.md +0 -123
  30. package/dist/default_templates/architecture/monorepo.md +0 -146
  31. package/dist/default_templates/architecture/nx.md +0 -122
  32. package/dist/default_templates/architecture/turborepo.md +0 -114
  33. package/dist/default_templates/ci/github-actions.md +0 -268
  34. package/dist/default_templates/ci/gitlab-ci.md +0 -330
  35. package/dist/default_templates/containers/docker-multistage.md +0 -120
  36. package/dist/default_templates/containers/kubernetes-deploy.md +0 -210
  37. package/dist/default_templates/devops/docker-compose.md +0 -111
  38. package/dist/default_templates/devops/docker-dockerfile.md +0 -94
  39. package/dist/default_templates/devops/github-actions.md +0 -160
  40. package/dist/default_templates/devops/gitlab-ci.md +0 -210
  41. package/dist/default_templates/dotnet/framework-aspnetcore.md +0 -205
  42. package/dist/default_templates/dotnet/framework-blazor.md +0 -271
  43. package/dist/default_templates/dotnet/lang-csharp.md +0 -162
  44. package/dist/default_templates/generic/lang-typescript.md +0 -11
  45. package/dist/default_templates/generic/state-redux.md +0 -21
  46. package/dist/default_templates/generic/state-rxjs.md +0 -6
  47. package/dist/default_templates/generic/style-mui.md +0 -23
  48. package/dist/default_templates/generic/style-tailwind.md +0 -6
  49. package/dist/default_templates/generic/test-cypress.md +0 -21
  50. package/dist/default_templates/generic/test-jest.md +0 -20
  51. package/dist/default_templates/generic/test-playwright.md +0 -21
  52. package/dist/default_templates/generic/test-vitest.md +0 -6
  53. package/dist/default_templates/go/lang-go.md +0 -571
  54. package/dist/default_templates/java/build-gradle.md +0 -102
  55. package/dist/default_templates/java/build-maven.md +0 -86
  56. package/dist/default_templates/java/framework-spring-boot.md +0 -179
  57. package/dist/default_templates/java/lang-java.md +0 -78
  58. package/dist/default_templates/java/lang-kotlin.md +0 -88
  59. package/dist/default_templates/meta/magic-helix-meta.md +0 -213
  60. package/dist/default_templates/meta/meta-debug.md +0 -459
  61. package/dist/default_templates/meta/meta-implement.md +0 -450
  62. package/dist/default_templates/meta/meta-roadmap.md +0 -265
  63. package/dist/default_templates/nestjs/nestjs-core.md +0 -7
  64. package/dist/default_templates/patterns/architecture/clean-architecture.md +0 -469
  65. package/dist/default_templates/patterns/architecture/dependency-injection.md +0 -517
  66. package/dist/default_templates/patterns/architecture/domain-driven-design.md +0 -621
  67. package/dist/default_templates/patterns/architecture/layered-architecture.md +0 -382
  68. package/dist/default_templates/patterns/architecture/repository-pattern.md +0 -408
  69. package/dist/default_templates/patterns/domain-expertise/nextjs-rules.md +0 -115
  70. package/dist/default_templates/patterns/domain-expertise/react-patterns.md +0 -181
  71. package/dist/default_templates/patterns/domain-expertise/server-components.md +0 -212
  72. package/dist/default_templates/patterns/domain-expertise/shadcn-ui.md +0 -52
  73. package/dist/default_templates/patterns/domain-expertise/tailwind-patterns.md +0 -52
  74. package/dist/default_templates/patterns/environment/container-awareness.md +0 -17
  75. package/dist/default_templates/patterns/environment/ide-features.md +0 -17
  76. package/dist/default_templates/patterns/environment/os-commands.md +0 -17
  77. package/dist/default_templates/patterns/organization/heading-hierarchy.md +0 -103
  78. package/dist/default_templates/patterns/organization/sequential-workflows.md +0 -102
  79. package/dist/default_templates/patterns/organization/xml-rule-groups.md +0 -64
  80. package/dist/default_templates/patterns/reasoning/agent-loop.md +0 -151
  81. package/dist/default_templates/patterns/reasoning/confirmation-gates.md +0 -141
  82. package/dist/default_templates/patterns/reasoning/dependency-analysis.md +0 -132
  83. package/dist/default_templates/patterns/reasoning/one-tool-per-iteration.md +0 -152
  84. package/dist/default_templates/patterns/reasoning/preview-before-action.md +0 -194
  85. package/dist/default_templates/patterns/reasoning/reflection-checkpoints.md +0 -166
  86. package/dist/default_templates/patterns/reasoning/result-verification.md +0 -157
  87. package/dist/default_templates/patterns/reasoning/subtask-breakdown.md +0 -131
  88. package/dist/default_templates/patterns/reasoning/thinking-tags.md +0 -100
  89. package/dist/default_templates/patterns/role-definition/capability-declarations.md +0 -72
  90. package/dist/default_templates/patterns/role-definition/expert-identity.md +0 -45
  91. package/dist/default_templates/patterns/role-definition/scope-boundaries.md +0 -61
  92. package/dist/default_templates/patterns/safety/code-safety-rules.md +0 -17
  93. package/dist/default_templates/patterns/safety/credential-handling.md +0 -17
  94. package/dist/default_templates/patterns/safety/destructive-warnings.md +0 -17
  95. package/dist/default_templates/patterns/safety/refusal-messages.md +0 -17
  96. package/dist/default_templates/patterns/tone/adaptive-tone.md +0 -17
  97. package/dist/default_templates/patterns/tone/concise-communication.md +0 -17
  98. package/dist/default_templates/patterns/tone/forbidden-phrases.md +0 -17
  99. package/dist/default_templates/patterns/tool-guidelines/function-schemas.md +0 -143
  100. package/dist/default_templates/patterns/tool-guidelines/parameter-examples.md +0 -137
  101. package/dist/default_templates/patterns/tool-guidelines/usage-policies.md +0 -105
  102. package/dist/default_templates/php/framework-laravel.md +0 -112
  103. package/dist/default_templates/php/lang-php.md +0 -94
  104. package/dist/default_templates/python/lang-python.md +0 -508
  105. package/dist/default_templates/react/react-core.md +0 -677
  106. package/dist/default_templates/react/react-zustand.md +0 -7
  107. package/dist/default_templates/ruby/framework-rails.md +0 -309
  108. package/dist/default_templates/ruby/framework-sinatra.md +0 -227
  109. package/dist/default_templates/ruby/lang-ruby.md +0 -216
  110. package/dist/default_templates/rust/lang-rust.md +0 -89
  111. package/dist/default_templates/swift/framework-vapor.md +0 -352
  112. package/dist/default_templates/swift/lang-swift.md +0 -291
  113. package/dist/default_templates/vue/style-primevue.md +0 -6
  114. package/dist/default_templates/vue/style-quasar.md +0 -22
  115. package/dist/default_templates/vue/vue-core.md +0 -28
  116. package/dist/default_templates/vue/vue-pinia.md +0 -5
  117. package/dist/index-AkVwRl-r.js +0 -92
  118. package/dist/index-AkVwRl-r.js.map +0 -1
  119. package/dist/index-B6BeG1yT.cjs +0 -68
  120. package/dist/index-B6BeG1yT.cjs.map +0 -1
  121. package/dist/index-B8pyjKdF.js +0 -94
  122. package/dist/index-B8pyjKdF.js.map +0 -1
  123. package/dist/index-B_6W_RnJ.cjs +0 -76
  124. package/dist/index-B_6W_RnJ.cjs.map +0 -1
  125. package/dist/index-Bg8DD8ku.js +0 -216
  126. package/dist/index-Bg8DD8ku.js.map +0 -1
  127. package/dist/index-BkJhe5Af.js +0 -1748
  128. package/dist/index-BkJhe5Af.js.map +0 -1
  129. package/dist/index-Bv4Q1Pr7.cjs +0 -33
  130. package/dist/index-Bv4Q1Pr7.cjs.map +0 -1
  131. package/dist/index-CN8J45Nc.cjs +0 -24
  132. package/dist/index-CN8J45Nc.cjs.map +0 -1
  133. package/dist/index-CPbv2Od1.js +0 -62
  134. package/dist/index-CPbv2Od1.js.map +0 -1
  135. package/dist/index-Cf-MC6Al.js +0 -63
  136. package/dist/index-Cf-MC6Al.js.map +0 -1
  137. package/dist/index-DDPXXXDy.cjs +0 -19
  138. package/dist/index-DDPXXXDy.cjs.map +0 -1
  139. package/dist/index-DO30AzDe.cjs +0 -19
  140. package/dist/index-DO30AzDe.cjs.map +0 -1
  141. package/dist/index-Dm37u5ut.js +0 -2128
  142. package/dist/index-Dm37u5ut.js.map +0 -1
  143. package/dist/index-DqHvgoXJ.cjs +0 -19
  144. package/dist/index-DqHvgoXJ.cjs.map +0 -1
  145. package/dist/index-J1qAfsnO.cjs +0 -2
  146. package/dist/index-J1qAfsnO.cjs.map +0 -1
  147. package/dist/index-Jz0HYZ7B.js +0 -13
  148. package/dist/index-Jz0HYZ7B.js.map +0 -1
  149. package/dist/index-K39pdw94.cjs +0 -31
  150. package/dist/index-K39pdw94.cjs.map +0 -1
  151. package/dist/index-L3IVvhd1.cjs +0 -89
  152. package/dist/index-L3IVvhd1.cjs.map +0 -1
  153. package/dist/index-OT2XAJkc.js +0 -117
  154. package/dist/index-OT2XAJkc.js.map +0 -1
  155. package/dist/index-TPAX4XKg.cjs +0 -30
  156. package/dist/index-TPAX4XKg.cjs.map +0 -1
  157. package/dist/index-WmVSB57y.js +0 -107
  158. package/dist/index-WmVSB57y.js.map +0 -1
  159. package/dist/index-mYXvc3Fs.js +0 -68
  160. package/dist/index-mYXvc3Fs.js.map +0 -1
@@ -1,21 +0,0 @@
1
- # Testing: Cypress
2
- - **ALWAYS** use `data-cy` attributes for element selection.
3
- - **ALWAYS** avoid using CSS selectors - prefer `data-cy` attributes.
4
- - **ALWAYS** use page object pattern for reusable selectors.
5
- - **ALWAYS** use `cy.intercept()` for API mocking and spying.
6
- - **ALWAYS** use `cy.wait()` sparingly - prefer assertions over arbitrary waits.
7
- - **ALWAYS** use `should()` for assertions instead of `then()`.
8
- - **ALWAYS** test user journeys, not implementation details.
9
- - **ALWAYS** use `cy.session()` for authentication state management.
10
- - **ALWAYS** organize tests in `cypress/e2e/` directory.
11
- - **ALWAYS** use `describe()` and `it()` for test organization.
12
- - **ALWAYS** use `beforeEach()` for test setup and navigation.
13
- - **ALWAYS** test both happy path and error scenarios.
14
- - **ALWAYS** use `cy.fixture()` for test data management.
15
- - **ALWAYS** use `cy.viewport()` to test responsive design.
16
- - **ALWAYS** use `cy.pause()` during development, remove for CI.
17
- - **ALWAYS** use `cy.debug()` for debugging, remove for production.
18
- - **ALWAYS** run tests in headless mode for CI/CD.
19
- - **ALWAYS** use `cy.request()` for API testing when UI testing isn't needed.
20
- - **ALWAYS** use custom commands for reusable actions.
21
- - **ALWAYS** maintain test execution time under 2 minutes per spec.
@@ -1,20 +0,0 @@
1
- # Testing: Jest
2
- - **ALWAYS** use descriptive test names that explain what is being tested.
3
- - **ALWAYS** follow the "Arrange, Act, Assert" pattern in tests.
4
- - **ALWAYS** use `describe` blocks to group related tests.
5
- - **ALWAYS** use `beforeEach` and `afterEach` for test setup and teardown.
6
- - **ALWAYS** mock external dependencies with `jest.mock()` or `jest.fn()`.
7
- - **ALWAYS** use `expect.assertions()` to ensure assertions are called.
8
- - **ALWAYS** test both success and error cases.
9
- - **ALWAYS** use `test.each` for parameterized tests.
10
- - **ALWAYS** avoid testing implementation details - test behavior.
11
- - **ALWAYS** use `jest.clearAllMocks()` in `beforeEach` for clean tests.
12
- - **ALWAYS** use meaningful assertion messages with `expect().toBe(expected, message)`.
13
- - **ALWAYS** test async code with `async/await` and proper error handling.
14
- - **ALWAYS** use `jest.spyOn()` for spying on object methods.
15
- - **ALWAYS** group mocks in a `mocks` directory with descriptive names.
16
- - **ALWAYS** use `jest.setTimeout()` for long-running async tests.
17
- - **ALWAYS** test edge cases and boundary conditions.
18
- - **ALWAYS** use `test.todo()` for planned tests.
19
- - **ALWAYS** maintain test coverage above 80%.
20
- - **ALWAYS** run tests in CI/CD pipelines with `--coverage` flag.
@@ -1,21 +0,0 @@
1
- # Testing: Playwright
2
- - **ALWAYS** use semantic locators like `getByRole()`, `getByLabel()`, `getByText()`.
3
- - **ALWAYS** use `data-testid` attributes as fallback for complex selectors.
4
- - **ALWAYS** use page object model for reusable selectors and actions.
5
- - **ALWAYS** use `page.route()` for API mocking and network interception.
6
- - **ALWAYS** use `expect().toBeVisible()` and `expect().toBeHidden()` for visibility checks.
7
- - **ALWAYS** use `page.waitForLoadState('networkidle')` for page load completion.
8
- - **ALWAYS** test cross-browser compatibility with different browser contexts.
9
- - **ALWAYS** use `test.describe()` for grouping related tests.
10
- - **ALWAYS** use `test.beforeEach()` for test setup and navigation.
11
- - **ALWAYS** use `page.context().storageState()` for authentication persistence.
12
- - **ALWAYS** use `page.screenshot()` for visual regression testing.
13
- - **ALWAYS** use `test.use()` for configuring test fixtures.
14
- - **ALWAYS** use `page.pause()` during development for debugging.
15
- - **ALWAYS** use `test.skip()` and `test.only()` for test management.
16
- - **ALWAYS** use `expect().toMatchSnapshot()` for visual comparisons.
17
- - **ALWAYS** use `page.keyboard` and `page.mouse` for complex interactions.
18
- - **ALWAYS** use `page.waitForFunction()` for custom wait conditions.
19
- - **ALWAYS** run tests in parallel with `workers` configuration.
20
- - **ALWAYS** use `testInfo.attachments` for test artifacts.
21
- - **ALWAYS** maintain test execution time under 30 seconds per test.
@@ -1,6 +0,0 @@
1
- # Testing: Vitest
2
- - **ALWAYS** use `describe`, `it`, and `expect` syntax.
3
- - **ALWAYS** mock dependencies using `vi.mock()`.
4
- - **ALWAYS** clean up mocks after each test using `afterEach(() => { vi.restoreAllMocks(); })`.
5
- - Use `it.todo('should do a thing')` for pending tests.
6
- - For component testing, prefer `@vitest/ui` for a visual runner.
@@ -1,571 +0,0 @@
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.