@ggailabs/cli-context 0.5.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @ggailabs/cli-context might be problematic. Click here for more details.

Files changed (282) hide show
  1. package/dist/.context/docs/GENESIS_SYSTEM_PROMPT.md +62 -0
  2. package/dist/.context/eng/agents/architect.md +15 -0
  3. package/dist/.context/eng/agents/backend_typescript.md +1000 -0
  4. package/dist/.context/eng/agents/bug_hunter.md +49 -0
  5. package/dist/.context/eng/agents/code_reviewer.md +313 -0
  6. package/dist/.context/eng/agents/devops_specialist.md +718 -0
  7. package/dist/.context/eng/agents/frontend_specialist.md +1027 -0
  8. package/dist/.context/eng/agents/qa_specialist.md +1234 -0
  9. package/dist/.context/eng/agents/security_reviewer.md +382 -0
  10. package/dist/.context/eng/agents/ui_specialist.md +16 -0
  11. package/dist/.context/eng/docs/AGENT_HANDOFF_PROMPT.md +44 -0
  12. package/dist/.context/eng/docs/GENESIS_DESIGN_SYSTEM.md +15 -0
  13. package/dist/.context/eng/docs/GG_METHODOLOGY.md +48 -0
  14. package/dist/.context/eng/docs/OPERATIONS_LOG.md +30 -0
  15. package/dist/.context/eng/docs/PROJECT_MAP.md +35 -0
  16. package/dist/.context/eng/docs/standards/devops.md +0 -0
  17. package/dist/.context/eng/docs/standards/frontend.md +0 -0
  18. package/dist/.context/eng/docs/standards/typescript.md +12 -0
  19. package/dist/.context/eng/skills/agent_logging.md +18 -0
  20. package/dist/.context/eng/skills/brainstorming.md +227 -0
  21. package/dist/.context/eng/skills/exploring_codebase.md +1217 -0
  22. package/dist/.context/eng/skills/patterns/compliance_check.md +246 -0
  23. package/dist/.context/eng/skills/patterns/coverage_table.md +401 -0
  24. package/dist/.context/eng/skills/patterns/exit_criteria.md +31 -0
  25. package/dist/.context/eng/skills/patterns/failure_recovery.md +74 -0
  26. package/dist/.context/eng/skills/patterns/quality_gate.md +295 -0
  27. package/dist/.context/eng/skills/patterns/standards_workflow.md +391 -0
  28. package/dist/.context/eng/skills/patterns/state_tracking.md +30 -0
  29. package/dist/.context/eng/skills/patterns/task_tracking.md +38 -0
  30. package/dist/.context/eng/skills/previce.md +45 -0
  31. package/dist/.context/eng/skills/tdd.md +421 -0
  32. package/dist/.context/eng/skills/writing_plans.md +105 -0
  33. package/dist/.context/plans/.gitkeep +0 -0
  34. package/dist/.context/pt-br/agents/architect.md +69 -0
  35. package/dist/.context/pt-br/agents/backend_typescript.md +1000 -0
  36. package/dist/.context/pt-br/agents/bug_hunter.md +49 -0
  37. package/dist/.context/pt-br/agents/code_reviewer.md +313 -0
  38. package/dist/.context/pt-br/agents/devops_specialist.md +718 -0
  39. package/dist/.context/pt-br/agents/frontend_specialist.md +1027 -0
  40. package/dist/.context/pt-br/agents/qa_specialist.md +1234 -0
  41. package/dist/.context/pt-br/agents/security_reviewer.md +382 -0
  42. package/dist/.context/pt-br/agents/ui_specialist.md +55 -0
  43. package/dist/.context/pt-br/docs/AGENT_HANDOFF_PROMPT.md +44 -0
  44. package/dist/.context/pt-br/docs/GENESIS_DESIGN_SYSTEM.md +88 -0
  45. package/dist/.context/pt-br/docs/GG_METHODOLOGY.md +48 -0
  46. package/dist/.context/pt-br/docs/OPERATIONS_LOG.md +30 -0
  47. package/dist/.context/pt-br/docs/PROJECT_MAP.md +37 -0
  48. package/dist/.context/pt-br/docs/standards/devops.md +707 -0
  49. package/dist/.context/pt-br/docs/standards/frontend.md +748 -0
  50. package/dist/.context/pt-br/docs/standards/typescript.md +1150 -0
  51. package/dist/.context/pt-br/skills/agent_logging.md +36 -0
  52. package/dist/.context/pt-br/skills/brainstorming.md +227 -0
  53. package/dist/.context/pt-br/skills/exploring_codebase.md +1217 -0
  54. package/dist/.context/pt-br/skills/patterns/compliance_check.md +246 -0
  55. package/dist/.context/pt-br/skills/patterns/coverage_table.md +401 -0
  56. package/dist/.context/pt-br/skills/patterns/exit_criteria.md +31 -0
  57. package/dist/.context/pt-br/skills/patterns/failure_recovery.md +74 -0
  58. package/dist/.context/pt-br/skills/patterns/quality_gate.md +295 -0
  59. package/dist/.context/pt-br/skills/patterns/standards_workflow.md +391 -0
  60. package/dist/.context/pt-br/skills/patterns/state_tracking.md +30 -0
  61. package/dist/.context/pt-br/skills/patterns/task_tracking.md +38 -0
  62. package/dist/.context/pt-br/skills/previce.md +45 -0
  63. package/dist/.context/pt-br/skills/tdd.md +421 -0
  64. package/dist/.context/pt-br/skills/writing_plans.md +105 -0
  65. package/dist/.context/workflow/.gitkeep +0 -0
  66. package/dist/commands/init.js +140 -0
  67. package/dist/commands/monitor.js +34 -0
  68. package/dist/index.js +20 -568
  69. package/dist/services/monitor-service.js +340 -0
  70. package/dist/services/scaffolder.js +164 -0
  71. package/package.json +16 -58
  72. package/LICENSE +0 -21
  73. package/README.md +0 -195
  74. package/dist/generators/agents/agentConfig.d.ts +0 -4
  75. package/dist/generators/agents/agentConfig.d.ts.map +0 -1
  76. package/dist/generators/agents/agentConfig.js +0 -180
  77. package/dist/generators/agents/agentConfig.js.map +0 -1
  78. package/dist/generators/agents/agentGenerator.d.ts +0 -9
  79. package/dist/generators/agents/agentGenerator.d.ts.map +0 -1
  80. package/dist/generators/agents/agentGenerator.js +0 -97
  81. package/dist/generators/agents/agentGenerator.js.map +0 -1
  82. package/dist/generators/agents/agentTypes.d.ts +0 -4
  83. package/dist/generators/agents/agentTypes.d.ts.map +0 -1
  84. package/dist/generators/agents/agentTypes.js +0 -25
  85. package/dist/generators/agents/agentTypes.js.map +0 -1
  86. package/dist/generators/agents/index.d.ts +0 -4
  87. package/dist/generators/agents/index.d.ts.map +0 -1
  88. package/dist/generators/agents/index.js +0 -12
  89. package/dist/generators/agents/index.js.map +0 -1
  90. package/dist/generators/agents/templates/index.d.ts +0 -4
  91. package/dist/generators/agents/templates/index.d.ts.map +0 -1
  92. package/dist/generators/agents/templates/index.js +0 -8
  93. package/dist/generators/agents/templates/index.js.map +0 -1
  94. package/dist/generators/agents/templates/indexTemplate.d.ts +0 -3
  95. package/dist/generators/agents/templates/indexTemplate.d.ts.map +0 -1
  96. package/dist/generators/agents/templates/indexTemplate.js +0 -36
  97. package/dist/generators/agents/templates/indexTemplate.js.map +0 -1
  98. package/dist/generators/agents/templates/playbookTemplate.d.ts +0 -4
  99. package/dist/generators/agents/templates/playbookTemplate.d.ts.map +0 -1
  100. package/dist/generators/agents/templates/playbookTemplate.js +0 -99
  101. package/dist/generators/agents/templates/playbookTemplate.js.map +0 -1
  102. package/dist/generators/agents/templates/types.d.ts +0 -14
  103. package/dist/generators/agents/templates/types.d.ts.map +0 -1
  104. package/dist/generators/agents/templates/types.js +0 -3
  105. package/dist/generators/agents/templates/types.js.map +0 -1
  106. package/dist/generators/documentation/documentationGenerator.d.ts +0 -15
  107. package/dist/generators/documentation/documentationGenerator.d.ts.map +0 -1
  108. package/dist/generators/documentation/documentationGenerator.js +0 -188
  109. package/dist/generators/documentation/documentationGenerator.js.map +0 -1
  110. package/dist/generators/documentation/guideRegistry.d.ts +0 -6
  111. package/dist/generators/documentation/guideRegistry.d.ts.map +0 -1
  112. package/dist/generators/documentation/guideRegistry.js +0 -82
  113. package/dist/generators/documentation/guideRegistry.js.map +0 -1
  114. package/dist/generators/documentation/index.d.ts +0 -2
  115. package/dist/generators/documentation/index.d.ts.map +0 -1
  116. package/dist/generators/documentation/index.js +0 -6
  117. package/dist/generators/documentation/index.js.map +0 -1
  118. package/dist/generators/documentation/templates/apiReferenceTemplate.d.ts +0 -2
  119. package/dist/generators/documentation/templates/apiReferenceTemplate.d.ts.map +0 -1
  120. package/dist/generators/documentation/templates/apiReferenceTemplate.js +0 -490
  121. package/dist/generators/documentation/templates/apiReferenceTemplate.js.map +0 -1
  122. package/dist/generators/documentation/templates/architectureTemplate.d.ts +0 -3
  123. package/dist/generators/documentation/templates/architectureTemplate.d.ts.map +0 -1
  124. package/dist/generators/documentation/templates/architectureTemplate.js +0 -66
  125. package/dist/generators/documentation/templates/architectureTemplate.js.map +0 -1
  126. package/dist/generators/documentation/templates/common.d.ts +0 -7
  127. package/dist/generators/documentation/templates/common.d.ts.map +0 -1
  128. package/dist/generators/documentation/templates/common.js +0 -58
  129. package/dist/generators/documentation/templates/common.js.map +0 -1
  130. package/dist/generators/documentation/templates/dataFlowTemplate.d.ts +0 -3
  131. package/dist/generators/documentation/templates/dataFlowTemplate.d.ts.map +0 -1
  132. package/dist/generators/documentation/templates/dataFlowTemplate.js +0 -40
  133. package/dist/generators/documentation/templates/dataFlowTemplate.js.map +0 -1
  134. package/dist/generators/documentation/templates/developmentWorkflowTemplate.d.ts +0 -2
  135. package/dist/generators/documentation/templates/developmentWorkflowTemplate.d.ts.map +0 -1
  136. package/dist/generators/documentation/templates/developmentWorkflowTemplate.js +0 -44
  137. package/dist/generators/documentation/templates/developmentWorkflowTemplate.js.map +0 -1
  138. package/dist/generators/documentation/templates/glossaryTemplate.d.ts +0 -3
  139. package/dist/generators/documentation/templates/glossaryTemplate.d.ts.map +0 -1
  140. package/dist/generators/documentation/templates/glossaryTemplate.js +0 -41
  141. package/dist/generators/documentation/templates/glossaryTemplate.js.map +0 -1
  142. package/dist/generators/documentation/templates/index.d.ts +0 -15
  143. package/dist/generators/documentation/templates/index.d.ts.map +0 -1
  144. package/dist/generators/documentation/templates/index.js +0 -30
  145. package/dist/generators/documentation/templates/index.js.map +0 -1
  146. package/dist/generators/documentation/templates/indexTemplate.d.ts +0 -3
  147. package/dist/generators/documentation/templates/indexTemplate.d.ts.map +0 -1
  148. package/dist/generators/documentation/templates/indexTemplate.js +0 -42
  149. package/dist/generators/documentation/templates/indexTemplate.js.map +0 -1
  150. package/dist/generators/documentation/templates/migrationTemplate.d.ts +0 -2
  151. package/dist/generators/documentation/templates/migrationTemplate.d.ts.map +0 -1
  152. package/dist/generators/documentation/templates/migrationTemplate.js +0 -422
  153. package/dist/generators/documentation/templates/migrationTemplate.js.map +0 -1
  154. package/dist/generators/documentation/templates/onboardingTemplate.d.ts +0 -2
  155. package/dist/generators/documentation/templates/onboardingTemplate.d.ts.map +0 -1
  156. package/dist/generators/documentation/templates/onboardingTemplate.js +0 -431
  157. package/dist/generators/documentation/templates/onboardingTemplate.js.map +0 -1
  158. package/dist/generators/documentation/templates/projectOverviewTemplate.d.ts +0 -3
  159. package/dist/generators/documentation/templates/projectOverviewTemplate.d.ts.map +0 -1
  160. package/dist/generators/documentation/templates/projectOverviewTemplate.js +0 -65
  161. package/dist/generators/documentation/templates/projectOverviewTemplate.js.map +0 -1
  162. package/dist/generators/documentation/templates/securityTemplate.d.ts +0 -2
  163. package/dist/generators/documentation/templates/securityTemplate.d.ts.map +0 -1
  164. package/dist/generators/documentation/templates/securityTemplate.js +0 -39
  165. package/dist/generators/documentation/templates/securityTemplate.js.map +0 -1
  166. package/dist/generators/documentation/templates/testingTemplate.d.ts +0 -2
  167. package/dist/generators/documentation/templates/testingTemplate.d.ts.map +0 -1
  168. package/dist/generators/documentation/templates/testingTemplate.js +0 -45
  169. package/dist/generators/documentation/templates/testingTemplate.js.map +0 -1
  170. package/dist/generators/documentation/templates/toolingTemplate.d.ts +0 -2
  171. package/dist/generators/documentation/templates/toolingTemplate.d.ts.map +0 -1
  172. package/dist/generators/documentation/templates/toolingTemplate.js +0 -42
  173. package/dist/generators/documentation/templates/toolingTemplate.js.map +0 -1
  174. package/dist/generators/documentation/templates/troubleshootingTemplate.d.ts +0 -2
  175. package/dist/generators/documentation/templates/troubleshootingTemplate.d.ts.map +0 -1
  176. package/dist/generators/documentation/templates/troubleshootingTemplate.js +0 -292
  177. package/dist/generators/documentation/templates/troubleshootingTemplate.js.map +0 -1
  178. package/dist/generators/documentation/templates/types.d.ts +0 -23
  179. package/dist/generators/documentation/templates/types.d.ts.map +0 -1
  180. package/dist/generators/documentation/templates/types.js +0 -3
  181. package/dist/generators/documentation/templates/types.js.map +0 -1
  182. package/dist/generators/plans/index.d.ts +0 -2
  183. package/dist/generators/plans/index.d.ts.map +0 -1
  184. package/dist/generators/plans/index.js +0 -6
  185. package/dist/generators/plans/index.js.map +0 -1
  186. package/dist/generators/plans/planGenerator.d.ts +0 -22
  187. package/dist/generators/plans/planGenerator.d.ts.map +0 -1
  188. package/dist/generators/plans/planGenerator.js +0 -109
  189. package/dist/generators/plans/planGenerator.js.map +0 -1
  190. package/dist/generators/plans/templates/indexTemplate.d.ts +0 -3
  191. package/dist/generators/plans/templates/indexTemplate.d.ts.map +0 -1
  192. package/dist/generators/plans/templates/indexTemplate.js +0 -37
  193. package/dist/generators/plans/templates/indexTemplate.js.map +0 -1
  194. package/dist/generators/plans/templates/planTemplate.d.ts +0 -3
  195. package/dist/generators/plans/templates/planTemplate.d.ts.map +0 -1
  196. package/dist/generators/plans/templates/planTemplate.js +0 -166
  197. package/dist/generators/plans/templates/planTemplate.js.map +0 -1
  198. package/dist/generators/plans/templates/types.d.ts +0 -19
  199. package/dist/generators/plans/templates/types.d.ts.map +0 -1
  200. package/dist/generators/plans/templates/types.js +0 -3
  201. package/dist/generators/plans/templates/types.js.map +0 -1
  202. package/dist/generators/shared/contextGenerator.d.ts +0 -7
  203. package/dist/generators/shared/contextGenerator.d.ts.map +0 -1
  204. package/dist/generators/shared/contextGenerator.js +0 -13
  205. package/dist/generators/shared/contextGenerator.js.map +0 -1
  206. package/dist/generators/shared/directoryTemplateHelpers.d.ts +0 -2
  207. package/dist/generators/shared/directoryTemplateHelpers.d.ts.map +0 -1
  208. package/dist/generators/shared/directoryTemplateHelpers.js +0 -12
  209. package/dist/generators/shared/directoryTemplateHelpers.js.map +0 -1
  210. package/dist/generators/shared/generatorUtils.d.ts +0 -16
  211. package/dist/generators/shared/generatorUtils.d.ts.map +0 -1
  212. package/dist/generators/shared/generatorUtils.js +0 -119
  213. package/dist/generators/shared/generatorUtils.js.map +0 -1
  214. package/dist/generators/shared/index.d.ts +0 -4
  215. package/dist/generators/shared/index.d.ts.map +0 -1
  216. package/dist/generators/shared/index.js +0 -10
  217. package/dist/generators/shared/index.js.map +0 -1
  218. package/dist/index.d.ts +0 -9
  219. package/dist/index.d.ts.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/prompts/defaults.d.ts +0 -3
  222. package/dist/prompts/defaults.d.ts.map +0 -1
  223. package/dist/prompts/defaults.js +0 -95
  224. package/dist/prompts/defaults.js.map +0 -1
  225. package/dist/services/baseLLMClient.d.ts +0 -12
  226. package/dist/services/baseLLMClient.d.ts.map +0 -1
  227. package/dist/services/baseLLMClient.js +0 -80
  228. package/dist/services/baseLLMClient.js.map +0 -1
  229. package/dist/services/fill/fillService.d.ts +0 -46
  230. package/dist/services/fill/fillService.d.ts.map +0 -1
  231. package/dist/services/fill/fillService.js +0 -254
  232. package/dist/services/fill/fillService.js.map +0 -1
  233. package/dist/services/init/initService.d.ts +0 -37
  234. package/dist/services/init/initService.d.ts.map +0 -1
  235. package/dist/services/init/initService.js +0 -167
  236. package/dist/services/init/initService.js.map +0 -1
  237. package/dist/services/llmClientFactory.d.ts +0 -8
  238. package/dist/services/llmClientFactory.d.ts.map +0 -1
  239. package/dist/services/llmClientFactory.js +0 -23
  240. package/dist/services/llmClientFactory.js.map +0 -1
  241. package/dist/services/openRouterClient.d.ts +0 -9
  242. package/dist/services/openRouterClient.d.ts.map +0 -1
  243. package/dist/services/openRouterClient.js +0 -49
  244. package/dist/services/openRouterClient.js.map +0 -1
  245. package/dist/services/plan/planService.d.ts +0 -57
  246. package/dist/services/plan/planService.d.ts.map +0 -1
  247. package/dist/services/plan/planService.js +0 -334
  248. package/dist/services/plan/planService.js.map +0 -1
  249. package/dist/services/shared/llmConfig.d.ts +0 -22
  250. package/dist/services/shared/llmConfig.d.ts.map +0 -1
  251. package/dist/services/shared/llmConfig.js +0 -38
  252. package/dist/services/shared/llmConfig.js.map +0 -1
  253. package/dist/types.d.ts +0 -65
  254. package/dist/types.d.ts.map +0 -1
  255. package/dist/types.js +0 -3
  256. package/dist/types.js.map +0 -1
  257. package/dist/utils/cliUI.d.ts +0 -27
  258. package/dist/utils/cliUI.d.ts.map +0 -1
  259. package/dist/utils/cliUI.js +0 -252
  260. package/dist/utils/cliUI.js.map +0 -1
  261. package/dist/utils/fileMapper.d.ts +0 -11
  262. package/dist/utils/fileMapper.d.ts.map +0 -1
  263. package/dist/utils/fileMapper.js +0 -146
  264. package/dist/utils/fileMapper.js.map +0 -1
  265. package/dist/utils/gitService.d.ts +0 -50
  266. package/dist/utils/gitService.d.ts.map +0 -1
  267. package/dist/utils/gitService.js +0 -470
  268. package/dist/utils/gitService.js.map +0 -1
  269. package/dist/utils/i18n.d.ts +0 -171
  270. package/dist/utils/i18n.d.ts.map +0 -1
  271. package/dist/utils/i18n.js +0 -381
  272. package/dist/utils/i18n.js.map +0 -1
  273. package/dist/utils/promptLoader.d.ts +0 -12
  274. package/dist/utils/promptLoader.d.ts.map +0 -1
  275. package/dist/utils/promptLoader.js +0 -81
  276. package/dist/utils/promptLoader.js.map +0 -1
  277. package/dist/utils/versionChecker.d.ts +0 -15
  278. package/dist/utils/versionChecker.d.ts.map +0 -1
  279. package/dist/utils/versionChecker.js +0 -49
  280. package/dist/utils/versionChecker.js.map +0 -1
  281. package/prompts/update_plan_prompt.md +0 -41
  282. package/prompts/update_scaffold_prompt.md +0 -47
@@ -0,0 +1,1150 @@
1
+ # TypeScript Standards
2
+
3
+ > **⚠️ MAINTENANCE:** This file is indexed in `dev-team/skills/shared-patterns/standards-coverage-table.md`.
4
+ > When adding/removing `## ` sections, follow FOUR-FILE UPDATE RULE in CLAUDE.md: (1) edit standards file, (2) update TOC, (3) update standards-coverage-table.md, (4) update agent file.
5
+
6
+ This file defines the specific standards for TypeScript (backend) development.
7
+
8
+ > **Reference**: Always consult `docs/PROJECT_RULES.md` for common project standards.
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ | # | Section | Description |
15
+ |---|---------|-------------|
16
+ | 1 | [Version](#version) | TypeScript and Node.js versions |
17
+ | 2 | [Strict Configuration](#strict-configuration-mandatory) | tsconfig.json requirements |
18
+ | 3 | [Frameworks & Libraries](#frameworks--libraries) | Required packages |
19
+ | 4 | [Type Safety](#type-safety) | Never use any, branded types |
20
+ | 5 | [Zod Validation Patterns](#zod-validation-patterns) | Schema validation |
21
+ | 6 | [Dependency Injection](#dependency-injection) | TSyringe patterns |
22
+ | 7 | [AsyncLocalStorage for Context](#asynclocalstorage-for-context) | Request context propagation |
23
+ | 8 | [Testing](#testing) | Type-safe mocks, fixtures |
24
+ | 9 | [Error Handling](#error-handling) | Custom error classes |
25
+ | 10 | [Function Design](#function-design-mandatory) | Single responsibility principle |
26
+ | 11 | [Naming Conventions](#naming-conventions) | Files, interfaces, types |
27
+ | 12 | [Directory Structure](#directory-structure) | Project layout (Lerian pattern) |
28
+ | 13 | [RabbitMQ Worker Pattern](#rabbitmq-worker-pattern) | Async message processing |
29
+ | 14 | [Always-Valid Domain Model](#always-valid-domain-model-mandatory) | Constructor validation, invariant protection |
30
+
31
+ **Meta-sections (not checked by agents):**
32
+ - [Checklist](#checklist) - Self-verification before submitting code
33
+
34
+ ---
35
+
36
+ ## Version
37
+
38
+ - TypeScript 5.0+
39
+ - Node.js 20+ / Deno 1.40+ / Bun 1.0+
40
+
41
+ ---
42
+
43
+ ## Strict Configuration (MANDATORY)
44
+
45
+ ```json
46
+ {
47
+ "compilerOptions": {
48
+ "strict": true,
49
+ "noUncheckedIndexedAccess": true,
50
+ "noImplicitOverride": true,
51
+ "noPropertyAccessFromIndexSignature": true,
52
+ "exactOptionalPropertyTypes": true,
53
+ "noFallthroughCasesInSwitch": true,
54
+ "noImplicitReturns": true,
55
+ "forceConsistentCasingInFileNames": true,
56
+ "skipLibCheck": false
57
+ }
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Frameworks & Libraries
64
+
65
+ ### Backend Frameworks
66
+
67
+ | Framework | Use Case |
68
+ |-----------|----------|
69
+ | Express | Traditional, widely adopted |
70
+ | Fastify | High performance |
71
+ | NestJS | Enterprise, Angular-style DI |
72
+ | Hono | Ultrafast, edge-ready |
73
+ | tRPC | End-to-end type safety |
74
+
75
+ ### ORMs & Query Builders
76
+
77
+ | Library | Use Case |
78
+ |---------|----------|
79
+ | Prisma | Type-safe ORM, migrations |
80
+ | Drizzle | Lightweight, SQL-like |
81
+ | TypeORM | Decorator-based ORM |
82
+ | Kysely | Type-safe query builder |
83
+
84
+ ### Validation
85
+
86
+ | Library | Use Case |
87
+ |---------|----------|
88
+ | Zod | Schema validation + types |
89
+ | Yup | Object schema validation |
90
+ | joi | Classic validation |
91
+ | class-validator | Decorator-based |
92
+
93
+ ### Testing
94
+
95
+ | Library | Use Case |
96
+ |---------|----------|
97
+ | Vitest | Fast, Vite-native |
98
+ | Jest | Full-featured |
99
+ | Supertest | HTTP testing |
100
+ | testcontainers | Integration tests |
101
+
102
+ ---
103
+
104
+ ## Type Safety
105
+
106
+ ### never use `any`
107
+
108
+ ```typescript
109
+ // FORBIDDEN
110
+ const data: any = fetchData();
111
+ function process(x: any) { ... }
112
+
113
+ // CORRECT - use unknown with type narrowing
114
+ const data: unknown = fetchData();
115
+ if (isUser(data)) {
116
+ console.log(data.name); // Now TypeScript knows it's User
117
+ }
118
+
119
+ // Type guard
120
+ function isUser(value: unknown): value is User {
121
+ return (
122
+ typeof value === 'object' &&
123
+ value !== null &&
124
+ 'id' in value &&
125
+ 'name' in value
126
+ );
127
+ }
128
+ ```
129
+
130
+ ### Branded Types for IDs
131
+
132
+ ```typescript
133
+ // Define branded type to prevent ID mixing
134
+ type Brand<T, B> = T & { __brand: B };
135
+
136
+ type UserId = Brand<string, 'UserId'>;
137
+ type TenantId = Brand<string, 'TenantId'>;
138
+ type OrderId = Brand<string, 'OrderId'>;
139
+
140
+ // Factory functions with validation
141
+ function createUserId(value: string): UserId {
142
+ if (!value.startsWith('usr_')) {
143
+ throw new Error('Invalid user ID format');
144
+ }
145
+ return value as UserId;
146
+ }
147
+
148
+ // Now TypeScript prevents mixing IDs
149
+ function getUser(id: UserId): User { ... }
150
+ function getOrder(id: OrderId): Order { ... }
151
+
152
+ const userId = createUserId('usr_123');
153
+ const orderId = createOrderId('ord_456');
154
+
155
+ getUser(userId); // OK
156
+ getUser(orderId); // TypeScript ERROR - type mismatch
157
+ ```
158
+
159
+ ### Discriminated Unions for State
160
+
161
+ ```typescript
162
+ // CORRECT - use discriminated unions
163
+ type RequestState<T> =
164
+ | { status: 'idle' }
165
+ | { status: 'loading' }
166
+ | { status: 'success'; data: T }
167
+ | { status: 'error'; error: Error };
168
+
169
+ function handleState(state: RequestState<User>) {
170
+ switch (state.status) {
171
+ case 'idle':
172
+ return null;
173
+ case 'loading':
174
+ return <Spinner />;
175
+ case 'success':
176
+ return <UserCard user={state.data} />; // TypeScript knows data exists
177
+ case 'error':
178
+ return <ErrorMessage error={state.error} />; // TypeScript knows error exists
179
+ }
180
+ }
181
+ ```
182
+
183
+ ### Result Type for Error Handling
184
+
185
+ ```typescript
186
+ // Define Result type
187
+ type Result<T, E = Error> =
188
+ | { success: true; data: T }
189
+ | { success: false; error: E };
190
+
191
+ // Usage
192
+ async function createUser(input: CreateUserInput): Promise<Result<User, ValidationError>> {
193
+ const validation = userSchema.safeParse(input);
194
+ if (!validation.success) {
195
+ return { success: false, error: new ValidationError(validation.error) };
196
+ }
197
+
198
+ const user = await db.user.create({ data: validation.data });
199
+ return { success: true, data: user };
200
+ }
201
+
202
+ // Pattern matching approach
203
+ const result = await createUser(input);
204
+ if (result.success) {
205
+ console.log(result.data.id); // TypeScript knows data exists
206
+ } else {
207
+ console.error(result.error.message); // TypeScript knows error exists
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Zod Validation Patterns
214
+
215
+ ### Schema Definition
216
+
217
+ ```typescript
218
+ import { z } from 'zod';
219
+
220
+ // Reusable primitives
221
+ const emailSchema = z.string().email();
222
+ const uuidSchema = z.string().uuid();
223
+ const moneySchema = z.number().positive().multipleOf(0.01);
224
+
225
+ // Compose schemas
226
+ const createUserSchema = z.object({
227
+ email: emailSchema,
228
+ name: z.string().min(1).max(100),
229
+ role: z.enum(['admin', 'user', 'guest']),
230
+ preferences: z.object({
231
+ theme: z.enum(['light', 'dark']).default('light'),
232
+ notifications: z.boolean().default(true),
233
+ }).optional(),
234
+ });
235
+
236
+ // Infer TypeScript type from schema
237
+ type CreateUserInput = z.infer<typeof createUserSchema>;
238
+
239
+ // Runtime validation
240
+ function createUser(input: unknown): CreateUserInput {
241
+ return createUserSchema.parse(input); // Throws on invalid
242
+ }
243
+
244
+ // Safe parsing (returns Result-like)
245
+ function validateUser(input: unknown) {
246
+ const result = createUserSchema.safeParse(input);
247
+ if (!result.success) {
248
+ return { error: result.error.flatten() };
249
+ }
250
+ return { data: result.data };
251
+ }
252
+ ```
253
+
254
+ ### Schema Composition
255
+
256
+ ```typescript
257
+ // Base schemas
258
+ const timestampSchema = z.object({
259
+ createdAt: z.date(),
260
+ updatedAt: z.date(),
261
+ });
262
+
263
+ const identifiableSchema = z.object({
264
+ id: uuidSchema,
265
+ });
266
+
267
+ // Compose for full entity
268
+ const userSchema = identifiableSchema
269
+ .merge(timestampSchema)
270
+ .extend({
271
+ email: emailSchema,
272
+ name: z.string(),
273
+ });
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Dependency Injection
279
+
280
+ ### Using TSyringe
281
+
282
+ ```typescript
283
+ import { container, injectable, inject } from 'tsyringe';
284
+
285
+ // Define interface
286
+ interface UserRepository {
287
+ findById(id: string): Promise<User | null>;
288
+ save(user: User): Promise<void>;
289
+ }
290
+
291
+ // Implement
292
+ @injectable()
293
+ class PostgresUserRepository implements UserRepository {
294
+ constructor(
295
+ @inject('Database') private db: Database
296
+ ) {}
297
+
298
+ async findById(id: string): Promise<User | null> {
299
+ return this.db.user.findUnique({ where: { id } });
300
+ }
301
+
302
+ async save(user: User): Promise<void> {
303
+ await this.db.user.upsert({ where: { id: user.id }, ...user });
304
+ }
305
+ }
306
+
307
+ // Service using repository
308
+ @injectable()
309
+ class UserService {
310
+ constructor(
311
+ @inject('UserRepository') private repo: UserRepository
312
+ ) {}
313
+
314
+ async getUser(id: string): Promise<User> {
315
+ const user = await this.repo.findById(id);
316
+ if (!user) throw new NotFoundError('User not found');
317
+ return user;
318
+ }
319
+ }
320
+
321
+ // Register in container
322
+ container.register('Database', { useClass: PrismaDatabase });
323
+ container.register('UserRepository', { useClass: PostgresUserRepository });
324
+
325
+ // Resolve
326
+ const userService = container.resolve(UserService);
327
+ ```
328
+
329
+ ---
330
+
331
+ ## AsyncLocalStorage for Context
332
+
333
+ ```typescript
334
+ import { AsyncLocalStorage } from 'async_hooks';
335
+
336
+ // Define context type
337
+ interface RequestContext {
338
+ requestId: string;
339
+ userId?: string;
340
+ tenantId?: string;
341
+ }
342
+
343
+ // Create storage
344
+ const asyncLocalStorage = new AsyncLocalStorage<RequestContext>();
345
+
346
+ // Get current context
347
+ export function getContext(): RequestContext {
348
+ const ctx = asyncLocalStorage.getStore();
349
+ if (!ctx) throw new Error('No context available');
350
+ return ctx;
351
+ }
352
+
353
+ // Middleware to set context
354
+ export function contextMiddleware(req: Request, res: Response, next: NextFunction) {
355
+ const context: RequestContext = {
356
+ requestId: req.headers['x-request-id'] as string || crypto.randomUUID(),
357
+ userId: req.user?.id,
358
+ tenantId: req.headers['x-tenant-id'] as string,
359
+ };
360
+
361
+ asyncLocalStorage.run(context, () => next());
362
+ }
363
+
364
+ // Usage anywhere in call chain
365
+ async function processOrder(orderId: string) {
366
+ const { tenantId, userId } = getContext();
367
+ logger.info('Processing order', { orderId, tenantId, userId });
368
+ // ...
369
+ }
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Testing
375
+
376
+ ### Type-Safe Mocks
377
+
378
+ ```typescript
379
+ import { vi, describe, it, expect } from 'vitest';
380
+
381
+ // Create typed mock
382
+ const mockUserRepository: jest.Mocked<UserRepository> = {
383
+ findById: vi.fn(),
384
+ save: vi.fn(),
385
+ };
386
+
387
+ describe('UserService', () => {
388
+ it('returns user when found', async () => {
389
+ // Arrange
390
+ const user: User = { id: 'usr_123', name: 'John', email: 'john@example.com' };
391
+ mockUserRepository.findById.mockResolvedValue(user);
392
+
393
+ const service = new UserService(mockUserRepository);
394
+
395
+ // Act
396
+ const result = await service.getUser('usr_123');
397
+
398
+ // Assert
399
+ expect(result).toEqual(user);
400
+ expect(mockUserRepository.findById).toHaveBeenCalledWith('usr_123');
401
+ });
402
+
403
+ it('throws NotFoundError when user not found', async () => {
404
+ // Arrange
405
+ mockUserRepository.findById.mockResolvedValue(null);
406
+
407
+ const service = new UserService(mockUserRepository);
408
+
409
+ // Act & Assert
410
+ await expect(service.getUser('usr_999')).rejects.toThrow(NotFoundError);
411
+ });
412
+ });
413
+ ```
414
+
415
+ ### Type-Safe Fixtures
416
+
417
+ ```typescript
418
+ // fixtures/user.ts
419
+ import { faker } from '@faker-js/faker';
420
+
421
+ export function createUserFixture(overrides: Partial<User> = {}): User {
422
+ return {
423
+ id: `usr_${faker.string.uuid()}`,
424
+ name: faker.person.fullName(),
425
+ email: faker.internet.email(),
426
+ createdAt: faker.date.past(),
427
+ updatedAt: new Date(),
428
+ ...overrides,
429
+ };
430
+ }
431
+
432
+ // Usage in tests
433
+ const user = createUserFixture({ name: 'Test User' });
434
+ ```
435
+
436
+ ### Edge Case Coverage (MANDATORY)
437
+
438
+ **Every acceptance criterion MUST have edge case tests beyond the happy path.**
439
+
440
+ | AC Type | Required Edge Cases | Minimum Count |
441
+ |---------|---------------------|---------------|
442
+ | Input validation | null, undefined, empty string, boundary values, invalid format, special chars | 3+ |
443
+ | CRUD operations | not found, duplicate, concurrent access, large payload | 3+ |
444
+ | Business logic | zero, negative, overflow, boundary conditions, invalid state | 3+ |
445
+ | Error handling | timeout, connection refused, invalid response, retry exhausted | 2+ |
446
+ | Authentication | expired token, invalid token, missing token, revoked token | 2+ |
447
+
448
+ **Edge Case Test Pattern:**
449
+
450
+ ```typescript
451
+ describe('UserService', () => {
452
+ describe('createUser', () => {
453
+ // Happy path
454
+ it('creates user with valid input', async () => {
455
+ const result = await service.createUser(validInput);
456
+ expect(result.id).toBeDefined();
457
+ });
458
+
459
+ // Edge cases (MANDATORY - minimum 3)
460
+ it('throws ValidationError for null input', async () => {
461
+ await expect(service.createUser(null as any)).rejects.toThrow(ValidationError);
462
+ });
463
+
464
+ it('throws ValidationError for empty email', async () => {
465
+ await expect(service.createUser({ ...validInput, email: '' })).rejects.toThrow(ValidationError);
466
+ });
467
+
468
+ it('throws ValidationError for invalid email format', async () => {
469
+ await expect(service.createUser({ ...validInput, email: 'invalid' })).rejects.toThrow(ValidationError);
470
+ });
471
+
472
+ it('throws ValidationError for email exceeding max length', async () => {
473
+ const longEmail = 'a'.repeat(256) + '@test.com';
474
+ await expect(service.createUser({ ...validInput, email: longEmail })).rejects.toThrow(ValidationError);
475
+ });
476
+
477
+ it('throws DuplicateError for existing email', async () => {
478
+ mockRepo.findByEmail.mockResolvedValue(existingUser);
479
+ await expect(service.createUser(validInput)).rejects.toThrow(DuplicateError);
480
+ });
481
+ });
482
+ });
483
+ ```
484
+
485
+ **Anti-Pattern (FORBIDDEN):**
486
+
487
+ ```typescript
488
+ // ❌ WRONG: Only happy path
489
+ describe('UserService', () => {
490
+ it('creates user', async () => {
491
+ const result = await service.createUser(validInput);
492
+ expect(result).toBeDefined(); // No edge cases = incomplete test
493
+ });
494
+ });
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Error Handling
500
+
501
+ ### Custom Error Classes
502
+
503
+ ```typescript
504
+ // Base application error
505
+ export class AppError extends Error {
506
+ constructor(
507
+ message: string,
508
+ public readonly code: string,
509
+ public readonly statusCode: number = 500,
510
+ public readonly details?: Record<string, unknown>
511
+ ) {
512
+ super(message);
513
+ this.name = this.constructor.name;
514
+ }
515
+
516
+ toJSON() {
517
+ return {
518
+ error: {
519
+ code: this.code,
520
+ message: this.message,
521
+ details: this.details,
522
+ },
523
+ };
524
+ }
525
+ }
526
+
527
+ // Specific errors
528
+ export class NotFoundError extends AppError {
529
+ constructor(resource: string) {
530
+ super(`${resource} not found`, 'NOT_FOUND', 404);
531
+ }
532
+ }
533
+
534
+ export class ValidationError extends AppError {
535
+ constructor(errors: z.ZodError) {
536
+ super('Validation failed', 'VALIDATION_ERROR', 400, {
537
+ fields: errors.flatten().fieldErrors,
538
+ });
539
+ }
540
+ }
541
+
542
+ export class UnauthorizedError extends AppError {
543
+ constructor(message = 'Unauthorized') {
544
+ super(message, 'UNAUTHORIZED', 401);
545
+ }
546
+ }
547
+ ```
548
+
549
+ ---
550
+
551
+ ## Function Design (MANDATORY)
552
+
553
+ **Single Responsibility Principle (SRP):** Each function MUST have exactly ONE responsibility.
554
+
555
+ ### Rules
556
+
557
+ | Rule | Description |
558
+ |------|-------------|
559
+ | **One responsibility per function** | A function should do ONE thing and do it well |
560
+ | **Max 20-30 lines** | If longer, break into smaller functions |
561
+ | **One level of abstraction** | Don't mix high-level and low-level operations |
562
+ | **Descriptive names** | Function name should describe its single responsibility |
563
+
564
+ ### Examples
565
+
566
+ ```typescript
567
+ // ❌ BAD - Multiple responsibilities
568
+ async function processOrder(order: Order): Promise<void> {
569
+ // Validate order
570
+ if (!order.items?.length) {
571
+ throw new Error('no items');
572
+ }
573
+ // Calculate total
574
+ let total = 0;
575
+ for (const item of order.items) {
576
+ total += item.price * item.quantity;
577
+ }
578
+ // Apply discount
579
+ if (order.couponCode) {
580
+ total = total * 0.9;
581
+ }
582
+ // Save to database
583
+ await db.orders.save(order);
584
+ // Send email
585
+ await sendEmail(order.customerEmail, 'Order confirmed');
586
+ }
587
+
588
+ // ✅ GOOD - Single responsibility per function
589
+ async function processOrder(order: Order): Promise<void> {
590
+ validateOrder(order);
591
+ const total = calculateTotal(order.items);
592
+ const finalTotal = applyDiscount(total, order.couponCode);
593
+ await saveOrder(order, finalTotal);
594
+ await notifyCustomer(order.customerEmail);
595
+ }
596
+
597
+ function validateOrder(order: Order): void {
598
+ if (!order.items?.length) {
599
+ throw new ValidationError('Order must have items');
600
+ }
601
+ }
602
+
603
+ function calculateTotal(items: OrderItem[]): number {
604
+ return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
605
+ }
606
+
607
+ function applyDiscount(total: number, couponCode?: string): number {
608
+ return couponCode ? total * 0.9 : total;
609
+ }
610
+ ```
611
+
612
+ ### Signs a Function Has Multiple Responsibilities
613
+
614
+ | Sign | Action |
615
+ |------|--------|
616
+ | Multiple `// section` comments | Split at comment boundaries |
617
+ | "and" in function name | Split into separate functions |
618
+ | More than 3 parameters | Consider parameter object or splitting |
619
+ | Nested conditionals > 2 levels | Extract inner logic to functions |
620
+ | Function does validation and processing | Separate validation function |
621
+
622
+ ---
623
+
624
+ ## Naming Conventions
625
+
626
+ | Element | Convention | Example |
627
+ |---------|------------|---------|
628
+ | Files | kebab-case | `user-service.ts` |
629
+ | Interfaces | PascalCase | `UserRepository` |
630
+ | Types | PascalCase | `CreateUserInput` |
631
+ | Functions | camelCase | `createUser` |
632
+ | Constants | UPPER_SNAKE | `MAX_RETRY_COUNT` |
633
+ | Enums | PascalCase + UPPER_SNAKE values | `UserRole.ADMIN` |
634
+
635
+ ---
636
+
637
+ ## Directory Structure
638
+
639
+ The directory structure follows the **Lerian pattern** - a simplified hexagonal architecture without explicit DDD folders.
640
+
641
+ ```
642
+ /src
643
+ /bootstrap # Application initialization
644
+ config.ts
645
+ server.ts
646
+ service.ts
647
+ /services # Business logic
648
+ /command # Write operations (use cases)
649
+ /query # Read operations (use cases)
650
+ /adapters # Infrastructure implementations
651
+ /http/in # HTTP handlers + routes
652
+ /grpc/in # gRPC handlers (if needed)
653
+ /postgres # PostgreSQL repositories
654
+ /mongodb # MongoDB repositories
655
+ /redis # Redis repositories
656
+ /rabbitmq # RabbitMQ producers/consumers
657
+ /lib # Utilities
658
+ db.ts
659
+ logger.ts
660
+ /types # Shared types and models
661
+ index.ts
662
+ /tests
663
+ /unit
664
+ /integration
665
+ ```
666
+
667
+ **Key differences from traditional DDD:**
668
+ - **No `/src/domain` folder** - Business entities live in `/src/types` or within service files
669
+ - **Services are the core** - `/src/services` contains all business logic (command/query pattern)
670
+ - **Adapters are flat** - Database repositories are organized by technology, not by domain
671
+
672
+ ---
673
+
674
+ ## RabbitMQ Worker Pattern
675
+
676
+ When the application includes async processing (API+Worker or Worker Only), follow this pattern.
677
+
678
+ ### Application Types
679
+
680
+ | Type | Characteristics | Components |
681
+ |------|----------------|------------|
682
+ | **API Only** | HTTP endpoints, no async processing | Handlers, Services, Repositories |
683
+ | **API + Worker** | HTTP endpoints + async message processing | All above + Consumers, Producers |
684
+ | **Worker Only** | No HTTP, only message processing | Consumers, Services, Repositories |
685
+
686
+ ### Architecture Overview
687
+
688
+ ```text
689
+ ┌─────────────────────────────────────────────────────────────┐
690
+ │ Service Bootstrap │
691
+ │ ├── HTTP Server (Express/Fastify) ← API endpoints │
692
+ │ ├── RabbitMQ Consumer ← Event-driven workers │
693
+ │ └── Redis Consumer (optional) ← Scheduled polling │
694
+ └─────────────────────────────────────────────────────────────┘
695
+ ```
696
+
697
+ ### Core Types
698
+
699
+ ```typescript
700
+ // Handler function signature
701
+ type QueueHandlerFunc = (ctx: Context, body: Buffer) => Promise<void>;
702
+
703
+ // Consumer configuration
704
+ interface ConsumerConfig {
705
+ connection: RabbitMQConnection;
706
+ routes: Map<string, QueueHandlerFunc>;
707
+ numberOfWorkers: number; // Workers per queue (default: 5)
708
+ prefetchCount: number; // QoS prefetch (default: 10)
709
+ logger: Logger;
710
+ telemetry: Telemetry;
711
+ }
712
+
713
+ // Context for handlers
714
+ interface Context {
715
+ requestId: string;
716
+ logger: Logger;
717
+ span: Span;
718
+ }
719
+ ```
720
+
721
+ ### Worker Configuration
722
+
723
+ | Config | Default | Purpose |
724
+ |--------|---------|---------|
725
+ | `RABBITMQ_NUMBERS_OF_WORKERS` | 5 | Concurrent workers per queue |
726
+ | `RABBITMQ_NUMBERS_OF_PREFETCH` | 10 | Messages buffered per worker |
727
+ | `RABBITMQ_CONSUMER_USER` | - | Separate credentials for consumer |
728
+ | `RABBITMQ_{QUEUE}_QUEUE` | - | Queue name per handler |
729
+
730
+ **Formula:** `Total buffered = Workers × Prefetch` (e.g., 5 × 10 = 50 messages)
731
+
732
+ ### Handler Registration
733
+
734
+ ```typescript
735
+ // Register handlers per queue
736
+ class MultiQueueConsumer {
737
+ registerRoutes(routes: ConsumerRoutes): void {
738
+ routes.register(
739
+ process.env.RABBITMQ_BALANCE_CREATE_QUEUE!,
740
+ this.handleBalanceCreate.bind(this)
741
+ );
742
+ routes.register(
743
+ process.env.RABBITMQ_TRANSACTION_QUEUE!,
744
+ this.handleTransaction.bind(this)
745
+ );
746
+ }
747
+ }
748
+ ```
749
+
750
+ ### Handler Implementation
751
+
752
+ ```typescript
753
+ async handleBalanceCreate(ctx: Context, body: Buffer): Promise<void> {
754
+ // 1. Parse and validate message
755
+ const parsed = queueMessageSchema.safeParse(JSON.parse(body.toString()));
756
+ if (!parsed.success) {
757
+ ctx.logger.error('Invalid message format', { error: parsed.error });
758
+ throw new Error(`Invalid message: ${parsed.error.message}`);
759
+ }
760
+
761
+ // 2. Execute business logic
762
+ const result = await this.useCase.createBalance(ctx, parsed.data);
763
+ if (!result.success) {
764
+ throw result.error;
765
+ }
766
+
767
+ // 3. Success → Ack automatically (by returning without error)
768
+ }
769
+ ```
770
+
771
+ ### Message Acknowledgment
772
+
773
+ | Result | Action | Effect |
774
+ |--------|--------|--------|
775
+ | Resolves | `msg.ack()` | Message removed from queue |
776
+ | Rejects/Throws | `msg.nack(false, true)` | Message requeued |
777
+
778
+ ### Worker Lifecycle
779
+
780
+ ```text
781
+ runConsumers()
782
+ ├── For each registered queue:
783
+ │ ├── ensureChannel() with exponential backoff
784
+ │ ├── Set QoS (prefetch)
785
+ │ ├── Start consume()
786
+ │ └── Process messages with concurrency limit
787
+
788
+ processMessage():
789
+ ├── Extract/generate TraceID from headers
790
+ ├── Create context with requestId
791
+ ├── Start OpenTelemetry span
792
+ ├── Call handler(ctx, msg.content)
793
+ ├── On success: msg.ack()
794
+ └── On error: log + msg.nack(false, true)
795
+ ```
796
+
797
+ ### Exponential Backoff with Jitter
798
+
799
+ ```typescript
800
+ const BACKOFF_CONFIG = {
801
+ maxRetries: 5,
802
+ initialBackoff: 500, // ms
803
+ maxBackoff: 10_000, // ms
804
+ backoffFactor: 2.0,
805
+ } as const;
806
+
807
+ function fullJitter(baseDelay: number): number {
808
+ const jitter = Math.random() * baseDelay;
809
+ return Math.min(jitter, BACKOFF_CONFIG.maxBackoff);
810
+ }
811
+
812
+ function nextBackoff(current: number): number {
813
+ const next = current * BACKOFF_CONFIG.backoffFactor;
814
+ return Math.min(next, BACKOFF_CONFIG.maxBackoff);
815
+ }
816
+ ```
817
+
818
+ ### Producer Implementation
819
+
820
+ ```typescript
821
+ class ProducerRepository {
822
+ async publish(
823
+ exchange: string,
824
+ routingKey: string,
825
+ message: unknown,
826
+ ctx: Context
827
+ ): Promise<void> {
828
+ await this.ensureChannel();
829
+
830
+ const headers = {
831
+ 'x-request-id': ctx.requestId,
832
+ ...injectTraceHeaders(ctx.span),
833
+ };
834
+
835
+ this.channel.publish(
836
+ exchange,
837
+ routingKey,
838
+ Buffer.from(JSON.stringify(message)),
839
+ {
840
+ contentType: 'application/json',
841
+ persistent: true,
842
+ headers,
843
+ }
844
+ );
845
+ }
846
+ }
847
+ ```
848
+
849
+ ### Message Schema with Zod
850
+
851
+ ```typescript
852
+ const queueDataSchema = z.object({
853
+ id: z.string().uuid(),
854
+ value: z.unknown(),
855
+ });
856
+
857
+ const queueMessageSchema = z.object({
858
+ organizationId: z.string().uuid(),
859
+ ledgerId: z.string().uuid(),
860
+ auditId: z.string().uuid(),
861
+ data: z.array(queueDataSchema),
862
+ });
863
+
864
+ type QueueMessage = z.infer<typeof queueMessageSchema>;
865
+ ```
866
+
867
+ ### Service Bootstrap (API + Worker)
868
+
869
+ ```typescript
870
+ class Service {
871
+ constructor(
872
+ private readonly server: HttpServer,
873
+ private readonly consumer: MultiQueueConsumer,
874
+ private readonly logger: Logger,
875
+ ) {}
876
+
877
+ async run(): Promise<void> {
878
+ // Run all components concurrently
879
+ await Promise.all([
880
+ this.server.listen(),
881
+ this.consumer.start(),
882
+ ]);
883
+
884
+ // Graceful shutdown
885
+ process.on('SIGTERM', async () => {
886
+ this.logger.info('Shutting down...');
887
+ await this.consumer.stop();
888
+ await this.server.close();
889
+ });
890
+ }
891
+ }
892
+ ```
893
+
894
+ ### Directory Structure for Workers
895
+
896
+ ```text
897
+ /src
898
+ /infrastructure
899
+ /rabbitmq
900
+ consumer.ts # ConsumerRoutes, worker pool
901
+ producer.ts # ProducerRepository
902
+ connection.ts # Connection management
903
+ /bootstrap
904
+ rabbitmq-server.ts # MultiQueueConsumer, handler registration
905
+ service.ts # Service orchestration
906
+ /lib
907
+ backoff.ts # Backoff utilities
908
+ /types
909
+ queue.ts # Message schemas
910
+ ```
911
+
912
+ ### Worker Checklist
913
+
914
+ - [ ] Handlers are idempotent (safe to process duplicates)
915
+ - [ ] Manual Ack enabled (`noAck: false`)
916
+ - [ ] Error handling throws error (triggers Nack)
917
+ - [ ] Context propagation with requestId
918
+ - [ ] OpenTelemetry spans for tracing
919
+ - [ ] Exponential backoff for connection recovery
920
+ - [ ] Graceful shutdown with proper cleanup
921
+ - [ ] Separate credentials for consumer vs producer
922
+ - [ ] Zod validation for all message payloads
923
+
924
+ ---
925
+
926
+ ## Always-Valid Domain Model (MANDATORY)
927
+
928
+ **HARD GATE:** All domain entities MUST use the Always-Valid Domain Model pattern. Anemic models (plain objects without validation) are FORBIDDEN.
929
+
930
+ ### Why This Pattern Is Mandatory
931
+
932
+ | Problem with Anemic Models | Impact |
933
+ |---------------------------|--------|
934
+ | Objects can exist in invalid state | Bugs propagate through system |
935
+ | Validation scattered across codebase | Duplication, inconsistency |
936
+ | Business rules not enforced at creation | Invalid data reaches database |
937
+ | No single source of truth for validity | Every consumer must re-validate |
938
+
939
+ ### The Pattern
940
+
941
+ **Core Principle:** An entity can NEVER exist in an invalid state. Validation happens in the factory, not later.
942
+
943
+ ```typescript
944
+ // ✅ CORRECT: Always-Valid Domain Model
945
+ class Rule {
946
+ private constructor(
947
+ private readonly _id: string,
948
+ private readonly _name: string,
949
+ private readonly _expression: string,
950
+ private readonly _createdAt: Date,
951
+ ) {}
952
+
953
+ // Factory method MUST validate and return Result
954
+ static create(name: string, expression: string): Result<Rule, ValidationError> {
955
+ // Validation at construction time
956
+ if (!name || name.trim().length === 0) {
957
+ return err(new ValidationError('name is required'));
958
+ }
959
+ if (name.length > 255) {
960
+ return err(new ValidationError('name exceeds 255 characters'));
961
+ }
962
+ if (!isValidExpression(expression)) {
963
+ return err(new ValidationError('invalid expression syntax'));
964
+ }
965
+
966
+ return ok(new Rule(
967
+ crypto.randomUUID(),
968
+ name.trim(),
969
+ expression,
970
+ new Date(),
971
+ ));
972
+ }
973
+
974
+ // Getters expose immutable data
975
+ get id(): string { return this._id; }
976
+ get name(): string { return this._name; }
977
+ get expression(): string { return this._expression; }
978
+ }
979
+ ```
980
+
981
+ ```typescript
982
+ // ❌ FORBIDDEN: Anemic Model (validation elsewhere)
983
+ interface Rule {
984
+ id: string;
985
+ name: string; // Can be empty - invalid!
986
+ expression: string; // Can be invalid - no validation!
987
+ }
988
+
989
+ // ❌ FORBIDDEN: Factory without validation
990
+ function createRule(name: string, expression: string): Rule {
991
+ return {
992
+ id: crypto.randomUUID(),
993
+ name, // No validation!
994
+ expression, // No validation!
995
+ };
996
+ }
997
+ ```
998
+
999
+ ### Requirements
1000
+
1001
+ | Requirement | Description |
1002
+ |-------------|-------------|
1003
+ | **Factory returns Result** | `Entity.create(...): Result<Entity, Error>` - MUST return error if invalid |
1004
+ | **Private constructor** | Prevent direct instantiation with `new` |
1005
+ | **Readonly properties** | Use `readonly` or getters to prevent mutation |
1006
+ | **No Setters** | Mutation through domain methods that validate |
1007
+ | **Invariants enforced** | Business rules validated at construction |
1008
+
1009
+ ### Mutation Pattern
1010
+
1011
+ When entities need to change state, use domain methods that validate:
1012
+
1013
+ ```typescript
1014
+ // ✅ CORRECT: Mutation with validation
1015
+ class Rule {
1016
+ // ...
1017
+
1018
+ updateExpression(newExpression: string): Result<void, ValidationError> {
1019
+ if (!isValidExpression(newExpression)) {
1020
+ return err(new ValidationError('invalid expression syntax'));
1021
+ }
1022
+ // TypeScript: use Object.assign or create new instance for immutability
1023
+ Object.assign(this, { _expression: newExpression });
1024
+ return ok(undefined);
1025
+ }
1026
+ }
1027
+
1028
+ // ❌ FORBIDDEN: Direct property assignment
1029
+ rule.expression = 'invalid!!!'; // Compilation error (readonly)
1030
+ ```
1031
+
1032
+ ### Reconstruction from Database
1033
+
1034
+ When loading from database, use a separate reconstruction method:
1035
+
1036
+ ```typescript
1037
+ // For repository use ONLY - reconstructs from trusted storage
1038
+ static reconstruct(
1039
+ id: string,
1040
+ name: string,
1041
+ expression: string,
1042
+ createdAt: Date,
1043
+ ): Rule {
1044
+ // Skip validation - data is from trusted storage
1045
+ return new Rule(id, name, expression, createdAt);
1046
+ }
1047
+ ```
1048
+
1049
+ **Note:** `reconstruct` methods skip validation because data is from trusted storage (already validated at creation).
1050
+
1051
+ ### Integration with HTTP Layer
1052
+
1053
+ HTTP handlers still use Zod for input validation, but MUST create domain entities via factories:
1054
+
1055
+ ```typescript
1056
+ // Zod schema - validation at boundary
1057
+ const createRuleSchema = z.object({
1058
+ name: z.string().min(1).max(255),
1059
+ expression: z.string().min(1),
1060
+ });
1061
+
1062
+ // Handler creates domain entity
1063
+ async function createRule(req: Request): Promise<Response> {
1064
+ // Boundary validation
1065
+ const parsed = createRuleSchema.safeParse(req.body);
1066
+ if (!parsed.success) {
1067
+ return errorResponse(parsed.error);
1068
+ }
1069
+
1070
+ // Domain entity creation - additional business validation
1071
+ const ruleResult = Rule.create(parsed.data.name, parsed.data.expression);
1072
+ if (ruleResult.isErr()) {
1073
+ return errorResponse(ruleResult.error);
1074
+ }
1075
+
1076
+ // ...
1077
+ }
1078
+ ```
1079
+
1080
+ ### Result Type Pattern
1081
+
1082
+ Use a Result type for operations that can fail:
1083
+
1084
+ ```typescript
1085
+ type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
1086
+
1087
+ function ok<T>(value: T): Result<T, never> {
1088
+ return { ok: true, value };
1089
+ }
1090
+
1091
+ function err<E>(error: E): Result<never, E> {
1092
+ return { ok: false, error };
1093
+ }
1094
+
1095
+ // Usage
1096
+ const result = Rule.create(name, expression);
1097
+ if (result.ok) {
1098
+ const rule = result.value;
1099
+ } else {
1100
+ const error = result.error;
1101
+ }
1102
+ ```
1103
+
1104
+ ### Anti-Rationalization Table
1105
+
1106
+ | Rationalization | Why It's WRONG | Required Action |
1107
+ |-----------------|----------------|-----------------|
1108
+ | "Zod validation at boundary is enough" | Boundary validation is for input format. Domain validation is for business rules. | **Use both: Zod validation + factory validation** |
1109
+ | "Adds boilerplate" | Invalid objects cause more work debugging than factories. | **Write the factory. It's an investment.** |
1110
+ | "We trust our code" | Every consumer must remember to validate. Humans forget. | **Enforce at construction. Forget-proof.** |
1111
+ | "Performance overhead" | Validation once at creation vs checking everywhere. | **Single validation is MORE efficient** |
1112
+ | "Existing code doesn't do this" | Technical debt. Refactor when touching the code. | **New code MUST follow. Refactor gradually.** |
1113
+ | "Plain interfaces are fine for DTOs" | DTOs are fine as plain objects. Domain entities are NOT. | **Distinguish DTO from Domain Entity** |
1114
+
1115
+ ### Checklist
1116
+
1117
+ - [ ] All domain entities use `private constructor` + `static create()` factory
1118
+ - [ ] Factories return `Result<Entity, Error>` - never throw
1119
+ - [ ] Properties are `readonly` or accessed via getters
1120
+ - [ ] Mutation through validated methods only
1121
+ - [ ] Reconstruct methods for database loading
1122
+ - [ ] No direct object instantiation outside factories
1123
+
1124
+ ---
1125
+
1126
+ ## Checklist
1127
+
1128
+ Before submitting TypeScript code, verify:
1129
+
1130
+ ### Type Safety
1131
+ - [ ] No `any` types (use `unknown` with narrowing)
1132
+ - [ ] Strict mode enabled in tsconfig.json
1133
+ - [ ] Zod validation for all external input
1134
+ - [ ] Branded types for IDs
1135
+ - [ ] Discriminated unions for state machines
1136
+ - [ ] Type inference used where possible (avoid redundant annotations)
1137
+ - [ ] No `@ts-ignore` or `@ts-expect-error` without explanation
1138
+
1139
+ ### Error Handling
1140
+ - [ ] Error classes extend base AppError
1141
+ - [ ] All async functions have proper error handling
1142
+ - [ ] Result type used for operations that can fail
1143
+
1144
+ ### DDD (if enabled)
1145
+ - [ ] Entities have identity comparison (`equals` method)
1146
+ - [ ] Value Objects are immutable (private constructor, factory methods)
1147
+ - [ ] Aggregates enforce invariants before state changes
1148
+ - [ ] Domain Events emitted for significant state changes
1149
+ - [ ] Repository interfaces defined in domain layer
1150
+ - [ ] No infrastructure dependencies in domain layer