qualia-framework 2.1.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.
Files changed (261) hide show
  1. package/README.md +50 -0
  2. package/bin/cli.js +519 -0
  3. package/framework/agents/architecture-strategist.md +53 -0
  4. package/framework/agents/backend-agent.md +150 -0
  5. package/framework/agents/code-simplicity-reviewer.md +86 -0
  6. package/framework/agents/frontend-agent.md +111 -0
  7. package/framework/agents/kieran-typescript-reviewer.md +96 -0
  8. package/framework/agents/performance-oracle.md +111 -0
  9. package/framework/agents/qualia-codebase-mapper.md +760 -0
  10. package/framework/agents/qualia-debugger.md +1203 -0
  11. package/framework/agents/qualia-executor.md +881 -0
  12. package/framework/agents/qualia-integration-checker.md +423 -0
  13. package/framework/agents/qualia-phase-researcher.md +453 -0
  14. package/framework/agents/qualia-plan-checker.md +699 -0
  15. package/framework/agents/qualia-planner.md +1241 -0
  16. package/framework/agents/qualia-project-researcher.md +602 -0
  17. package/framework/agents/qualia-research-synthesizer.md +236 -0
  18. package/framework/agents/qualia-roadmapper.md +605 -0
  19. package/framework/agents/qualia-verifier.md +685 -0
  20. package/framework/agents/team-orchestrator.md +228 -0
  21. package/framework/agents/teams/full-stack-team.md +48 -0
  22. package/framework/agents/teams/optimize-team.md +53 -0
  23. package/framework/agents/teams/review-team.md +62 -0
  24. package/framework/agents/teams/ship-team.md +86 -0
  25. package/framework/agents/test-agent.md +182 -0
  26. package/framework/askpass.sh +2 -0
  27. package/framework/commands/design.md +53 -0
  28. package/framework/commands/quick-db.md +22 -0
  29. package/framework/config/retention.json +35 -0
  30. package/framework/core/PRINCIPLES.md +77 -0
  31. package/framework/hooks/auto-format.sh +45 -0
  32. package/framework/hooks/block-env-edit.sh +42 -0
  33. package/framework/hooks/branch-guard.sh +46 -0
  34. package/framework/hooks/confirm-delete.sh +56 -0
  35. package/framework/hooks/migration-validate.sh +68 -0
  36. package/framework/hooks/notification-speak.sh +15 -0
  37. package/framework/hooks/pre-commit.sh +80 -0
  38. package/framework/hooks/pre-compact.sh +55 -0
  39. package/framework/hooks/pre-deploy-gate.sh +151 -0
  40. package/framework/hooks/qualia-colors.sh +32 -0
  41. package/framework/hooks/retention-cleanup.sh +43 -0
  42. package/framework/hooks/save-session-state.sh +153 -0
  43. package/framework/hooks/session-context-loader.sh +28 -0
  44. package/framework/hooks/session-learn.sh +30 -0
  45. package/framework/knowledge/claudecode-bible.md +1384 -0
  46. package/framework/knowledge/client-prefs.md +22 -0
  47. package/framework/knowledge/common-fixes.md +25 -0
  48. package/framework/knowledge/deployment-map.md +35 -0
  49. package/framework/knowledge/email-signature.html +1 -0
  50. package/framework/knowledge/employees.md +8 -0
  51. package/framework/knowledge/learned-patterns.md +51 -0
  52. package/framework/knowledge/optimization-research-2026.md +137 -0
  53. package/framework/knowledge/qualia-context.md +67 -0
  54. package/framework/knowledge/supabase-patterns.md +50 -0
  55. package/framework/knowledge/voice-agent-patterns.md +46 -0
  56. package/framework/qualia-engine/VERSION +1 -0
  57. package/framework/qualia-engine/bin/qualia-tools.js +2160 -0
  58. package/framework/qualia-engine/bin/qualia-tools.test.js +1054 -0
  59. package/framework/qualia-engine/references/checkpoints.md +775 -0
  60. package/framework/qualia-engine/references/continuation-format.md +249 -0
  61. package/framework/qualia-engine/references/decimal-phase-calculation.md +65 -0
  62. package/framework/qualia-engine/references/design-quality.md +56 -0
  63. package/framework/qualia-engine/references/git-integration.md +254 -0
  64. package/framework/qualia-engine/references/git-planning-commit.md +50 -0
  65. package/framework/qualia-engine/references/model-profile-resolution.md +32 -0
  66. package/framework/qualia-engine/references/model-profiles.md +73 -0
  67. package/framework/qualia-engine/references/phase-argument-parsing.md +61 -0
  68. package/framework/qualia-engine/references/planning-config.md +195 -0
  69. package/framework/qualia-engine/references/questioning.md +141 -0
  70. package/framework/qualia-engine/references/tdd.md +263 -0
  71. package/framework/qualia-engine/references/ui-brand.md +160 -0
  72. package/framework/qualia-engine/references/verification-patterns.md +612 -0
  73. package/framework/qualia-engine/templates/DEBUG.md +159 -0
  74. package/framework/qualia-engine/templates/DESIGN.md +81 -0
  75. package/framework/qualia-engine/templates/UAT.md +247 -0
  76. package/framework/qualia-engine/templates/codebase/architecture.md +255 -0
  77. package/framework/qualia-engine/templates/codebase/concerns.md +310 -0
  78. package/framework/qualia-engine/templates/codebase/conventions.md +307 -0
  79. package/framework/qualia-engine/templates/codebase/integrations.md +280 -0
  80. package/framework/qualia-engine/templates/codebase/stack.md +186 -0
  81. package/framework/qualia-engine/templates/codebase/structure.md +285 -0
  82. package/framework/qualia-engine/templates/codebase/testing.md +480 -0
  83. package/framework/qualia-engine/templates/config.json +35 -0
  84. package/framework/qualia-engine/templates/context.md +283 -0
  85. package/framework/qualia-engine/templates/continue-here.md +78 -0
  86. package/framework/qualia-engine/templates/debug-subagent-prompt.md +91 -0
  87. package/framework/qualia-engine/templates/discovery.md +146 -0
  88. package/framework/qualia-engine/templates/milestone-archive.md +123 -0
  89. package/framework/qualia-engine/templates/milestone.md +115 -0
  90. package/framework/qualia-engine/templates/phase-prompt.md +567 -0
  91. package/framework/qualia-engine/templates/planner-subagent-prompt.md +117 -0
  92. package/framework/qualia-engine/templates/project.md +184 -0
  93. package/framework/qualia-engine/templates/projects/ai-agent.md +156 -0
  94. package/framework/qualia-engine/templates/projects/mobile-app.md +181 -0
  95. package/framework/qualia-engine/templates/projects/voice-agent.md +134 -0
  96. package/framework/qualia-engine/templates/projects/website.md +137 -0
  97. package/framework/qualia-engine/templates/requirements.md +231 -0
  98. package/framework/qualia-engine/templates/research-project/ARCHITECTURE.md +204 -0
  99. package/framework/qualia-engine/templates/research-project/FEATURES.md +147 -0
  100. package/framework/qualia-engine/templates/research-project/PITFALLS.md +200 -0
  101. package/framework/qualia-engine/templates/research-project/STACK.md +120 -0
  102. package/framework/qualia-engine/templates/research-project/SUMMARY.md +170 -0
  103. package/framework/qualia-engine/templates/research.md +552 -0
  104. package/framework/qualia-engine/templates/roadmap.md +202 -0
  105. package/framework/qualia-engine/templates/state.md +176 -0
  106. package/framework/qualia-engine/templates/summary-complex.md +59 -0
  107. package/framework/qualia-engine/templates/summary-minimal.md +41 -0
  108. package/framework/qualia-engine/templates/summary-standard.md +48 -0
  109. package/framework/qualia-engine/templates/summary.md +246 -0
  110. package/framework/qualia-engine/templates/user-setup.md +311 -0
  111. package/framework/qualia-engine/templates/verification-report.md +322 -0
  112. package/framework/qualia-engine/workflows/add-phase.md +179 -0
  113. package/framework/qualia-engine/workflows/add-todo.md +157 -0
  114. package/framework/qualia-engine/workflows/audit-milestone.md +241 -0
  115. package/framework/qualia-engine/workflows/check-todos.md +176 -0
  116. package/framework/qualia-engine/workflows/complete-milestone.md +858 -0
  117. package/framework/qualia-engine/workflows/diagnose-issues.md +219 -0
  118. package/framework/qualia-engine/workflows/discovery-phase.md +289 -0
  119. package/framework/qualia-engine/workflows/discuss-phase.md +534 -0
  120. package/framework/qualia-engine/workflows/execute-phase.md +559 -0
  121. package/framework/qualia-engine/workflows/execute-plan.md +438 -0
  122. package/framework/qualia-engine/workflows/help.md +470 -0
  123. package/framework/qualia-engine/workflows/insert-phase.md +220 -0
  124. package/framework/qualia-engine/workflows/list-phase-assumptions.md +178 -0
  125. package/framework/qualia-engine/workflows/map-codebase.md +327 -0
  126. package/framework/qualia-engine/workflows/new-milestone.md +363 -0
  127. package/framework/qualia-engine/workflows/new-project.md +1037 -0
  128. package/framework/qualia-engine/workflows/pause-work.md +122 -0
  129. package/framework/qualia-engine/workflows/plan-milestone-gaps.md +256 -0
  130. package/framework/qualia-engine/workflows/plan-phase.md +422 -0
  131. package/framework/qualia-engine/workflows/progress.md +354 -0
  132. package/framework/qualia-engine/workflows/quick.md +252 -0
  133. package/framework/qualia-engine/workflows/remove-phase.md +326 -0
  134. package/framework/qualia-engine/workflows/research-phase.md +74 -0
  135. package/framework/qualia-engine/workflows/resume-project.md +306 -0
  136. package/framework/qualia-engine/workflows/set-profile.md +80 -0
  137. package/framework/qualia-engine/workflows/settings.md +145 -0
  138. package/framework/qualia-engine/workflows/transition.md +556 -0
  139. package/framework/qualia-engine/workflows/update.md +197 -0
  140. package/framework/qualia-engine/workflows/verify-phase.md +195 -0
  141. package/framework/qualia-engine/workflows/verify-work.md +625 -0
  142. package/framework/rules/context7.md +11 -0
  143. package/framework/rules/deployment.md +29 -0
  144. package/framework/rules/frontend.md +33 -0
  145. package/framework/rules/security.md +12 -0
  146. package/framework/rules/speed.md +20 -0
  147. package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
  148. package/framework/scripts/apply-retention.sh +120 -0
  149. package/framework/scripts/bootstrap-pop-os.sh +354 -0
  150. package/framework/scripts/claude-voice +13 -0
  151. package/framework/scripts/cleanup.sh +131 -0
  152. package/framework/scripts/cowork-mode.sh +141 -0
  153. package/framework/scripts/generate-project-claude-md.sh +153 -0
  154. package/framework/scripts/load-test-webhook.js +172 -0
  155. package/framework/scripts/say.py +236 -0
  156. package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +167 -0
  157. package/framework/scripts/showcase-video-recorder/playwright-helpers.js +216 -0
  158. package/framework/scripts/speak.py +55 -0
  159. package/framework/scripts/speak.sh +18 -0
  160. package/framework/scripts/status.sh +138 -0
  161. package/framework/scripts/sync-to-framework.sh +65 -0
  162. package/framework/scripts/voice-hotkey.py +227 -0
  163. package/framework/scripts/voice-input.sh +51 -0
  164. package/framework/skills/animate/SKILL.md +202 -0
  165. package/framework/skills/bolder/SKILL.md +144 -0
  166. package/framework/skills/browser-qa/SKILL.md +536 -0
  167. package/framework/skills/clarify/SKILL.md +179 -0
  168. package/framework/skills/colorize/SKILL.md +170 -0
  169. package/framework/skills/critique/SKILL.md +126 -0
  170. package/framework/skills/deep-research/SKILL.md +271 -0
  171. package/framework/skills/delight/SKILL.md +329 -0
  172. package/framework/skills/deploy/SKILL.md +261 -0
  173. package/framework/skills/deploy-verify/SKILL.md +377 -0
  174. package/framework/skills/deploy-verify/scripts/canary-check.sh +206 -0
  175. package/framework/skills/deploy-verify/scripts/check-console-errors.js +147 -0
  176. package/framework/skills/deploy-verify/scripts/check-cwv.js +139 -0
  177. package/framework/skills/deploy-verify/scripts/project-detect.sh +84 -0
  178. package/framework/skills/deploy-verify/scripts/verify.sh +548 -0
  179. package/framework/skills/design-quieter/SKILL.md +130 -0
  180. package/framework/skills/distill/SKILL.md +149 -0
  181. package/framework/skills/docs-lookup/SKILL.md +78 -0
  182. package/framework/skills/fcm-notifications/SKILL.md +125 -0
  183. package/framework/skills/financial-ledger/SKILL.md +1039 -0
  184. package/framework/skills/frontend-master/NOTICE.md +4 -0
  185. package/framework/skills/frontend-master/SKILL.md +127 -0
  186. package/framework/skills/frontend-master/reference/color-and-contrast.md +132 -0
  187. package/framework/skills/frontend-master/reference/interaction-design.md +123 -0
  188. package/framework/skills/frontend-master/reference/motion-design.md +99 -0
  189. package/framework/skills/frontend-master/reference/responsive-design.md +114 -0
  190. package/framework/skills/frontend-master/reference/spatial-design.md +100 -0
  191. package/framework/skills/frontend-master/reference/typography.md +131 -0
  192. package/framework/skills/frontend-master/reference/ux-writing.md +107 -0
  193. package/framework/skills/harden/SKILL.md +357 -0
  194. package/framework/skills/i18n-rtl/SKILL.md +752 -0
  195. package/framework/skills/learn/SKILL.md +71 -0
  196. package/framework/skills/memory/SKILL.md +50 -0
  197. package/framework/skills/mobile-expo/SKILL.md +864 -0
  198. package/framework/skills/mobile-expo/references/store-checklist.md +550 -0
  199. package/framework/skills/nestjs-backend/README.md +73 -0
  200. package/framework/skills/nestjs-backend/SKILL.md +446 -0
  201. package/framework/skills/nestjs-backend/references/templates.md +1173 -0
  202. package/framework/skills/normalize/SKILL.md +79 -0
  203. package/framework/skills/onboard/SKILL.md +242 -0
  204. package/framework/skills/polish/SKILL.md +209 -0
  205. package/framework/skills/pr/SKILL.md +66 -0
  206. package/framework/skills/qualia/SKILL.md +153 -0
  207. package/framework/skills/qualia-add-todo/SKILL.md +68 -0
  208. package/framework/skills/qualia-audit-milestone/SKILL.md +92 -0
  209. package/framework/skills/qualia-check-todos/SKILL.md +55 -0
  210. package/framework/skills/qualia-complete-milestone/SKILL.md +108 -0
  211. package/framework/skills/qualia-debug/SKILL.md +149 -0
  212. package/framework/skills/qualia-design/SKILL.md +203 -0
  213. package/framework/skills/qualia-discuss-phase/SKILL.md +72 -0
  214. package/framework/skills/qualia-execute-phase/SKILL.md +86 -0
  215. package/framework/skills/qualia-help/SKILL.md +67 -0
  216. package/framework/skills/qualia-idk/SKILL.md +352 -0
  217. package/framework/skills/qualia-list-phase-assumptions/SKILL.md +67 -0
  218. package/framework/skills/qualia-new-milestone/SKILL.md +72 -0
  219. package/framework/skills/qualia-new-project/SKILL.md +92 -0
  220. package/framework/skills/qualia-optimize/SKILL.md +417 -0
  221. package/framework/skills/qualia-pause-work/SKILL.md +96 -0
  222. package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +57 -0
  223. package/framework/skills/qualia-plan-phase/SKILL.md +101 -0
  224. package/framework/skills/qualia-progress/SKILL.md +53 -0
  225. package/framework/skills/qualia-quick/SKILL.md +89 -0
  226. package/framework/skills/qualia-research-phase/SKILL.md +88 -0
  227. package/framework/skills/qualia-resume-work/SKILL.md +62 -0
  228. package/framework/skills/qualia-review/SKILL.md +263 -0
  229. package/framework/skills/qualia-start/SKILL.md +182 -0
  230. package/framework/skills/qualia-verify-work/SKILL.md +105 -0
  231. package/framework/skills/qualia-workflow/SKILL.md +130 -0
  232. package/framework/skills/rag/SKILL.md +750 -0
  233. package/framework/skills/responsive/SKILL.md +231 -0
  234. package/framework/skills/retro/SKILL.md +284 -0
  235. package/framework/skills/sakani-conventions/SKILL.md +136 -0
  236. package/framework/skills/sakani-conventions/evals/evals.json +23 -0
  237. package/framework/skills/sakani-conventions/references/entities.md +365 -0
  238. package/framework/skills/sakani-conventions/references/error-codes.md +95 -0
  239. package/framework/skills/seo-master/SKILL.md +490 -0
  240. package/framework/skills/seo-master/references/checklist.md +199 -0
  241. package/framework/skills/seo-master/references/structured-data.md +609 -0
  242. package/framework/skills/ship/SKILL.md +202 -0
  243. package/framework/skills/stack-researcher/SKILL.md +215 -0
  244. package/framework/skills/status/SKILL.md +154 -0
  245. package/framework/skills/status/scripts/health-check.sh +562 -0
  246. package/framework/skills/subscription-payments/SKILL.md +250 -0
  247. package/framework/skills/supabase/SKILL.md +973 -0
  248. package/framework/skills/supabase/references/templates.md +159 -0
  249. package/framework/skills/team/SKILL.md +67 -0
  250. package/framework/skills/test-runner/SKILL.md +202 -0
  251. package/framework/skills/voice-agent/SKILL.md +407 -0
  252. package/framework/skills/zoho-workflow/SKILL.md +51 -0
  253. package/framework/statusline-command.sh +117 -0
  254. package/package.json +24 -0
  255. package/profiles/fawzi.json +16 -0
  256. package/profiles/hasan.json +16 -0
  257. package/profiles/moayad.json +16 -0
  258. package/templates/CLAUDE-owner.md +52 -0
  259. package/templates/CLAUDE.md.hbs +58 -0
  260. package/templates/env.claude.template +12 -0
  261. package/templates/settings.json +141 -0
@@ -0,0 +1,1173 @@
1
+ # NestJS Backend Templates
2
+
3
+ Production-quality starter templates for common NestJS patterns. All templates use Supabase as the database and Zod for validation.
4
+
5
+ ---
6
+
7
+ ## 1. Complete Feature Module
8
+
9
+ Full working example of a feature module with all layers (module, controller, service, DTO, spec).
10
+
11
+ ### building.module.ts
12
+
13
+ ```typescript
14
+ import { Module } from '@nestjs/common';
15
+ import { BuildingController } from './controllers/building.controller';
16
+ import { BuildingService } from './services/building.service';
17
+ import { SupabaseService } from '../database/supabase.service';
18
+ import { LoggerService } from '../logger/logger.service';
19
+
20
+ @Module({
21
+ controllers: [BuildingController],
22
+ providers: [BuildingService, SupabaseService, LoggerService],
23
+ exports: [BuildingService],
24
+ })
25
+ export class BuildingModule {}
26
+ ```
27
+
28
+ ### controllers/building.controller.ts
29
+
30
+ ```typescript
31
+ import {
32
+ Controller,
33
+ Get,
34
+ Post,
35
+ Body,
36
+ Param,
37
+ UseGuards,
38
+ BadRequestException,
39
+ } from '@nestjs/common';
40
+ import { BuildingService } from '../services/building.service';
41
+ import { AuthGuard } from '../../common/guards/auth.guard';
42
+ import { CurrentUser } from '../../common/decorators/current-user.decorator';
43
+ import { ZodValidationPipe } from '../../common/pipes/zod-validation.pipe';
44
+ import { JwtPayload } from '../../common/types/jwt.types';
45
+ import {
46
+ CreateBuildingDtoSchema,
47
+ CreateBuildingDto,
48
+ } from '../dtos/create-building.dto';
49
+ import {
50
+ UpdateBuildingDtoSchema,
51
+ UpdateBuildingDto,
52
+ } from '../dtos/update-building.dto';
53
+
54
+ @Controller('buildings')
55
+ @UseGuards(AuthGuard)
56
+ export class BuildingController {
57
+ constructor(private readonly buildingService: BuildingService) {}
58
+
59
+ @Post()
60
+ async create(
61
+ @Body(new ZodValidationPipe(CreateBuildingDtoSchema))
62
+ dto: CreateBuildingDto,
63
+ @CurrentUser() user: JwtPayload,
64
+ ) {
65
+ const building = await this.buildingService.create(dto, user.id);
66
+ return { data: building };
67
+ }
68
+
69
+ @Get()
70
+ async findAll(@CurrentUser() user: JwtPayload) {
71
+ const buildings = await this.buildingService.findByOwner(user.id);
72
+ return { data: buildings };
73
+ }
74
+
75
+ @Get(':id')
76
+ async findOne(
77
+ @Param('id') id: string,
78
+ @CurrentUser() user: JwtPayload,
79
+ ) {
80
+ const building = await this.buildingService.findById(id, user.id);
81
+ return { data: building };
82
+ }
83
+
84
+ @Post(':id')
85
+ async update(
86
+ @Param('id') id: string,
87
+ @Body(new ZodValidationPipe(UpdateBuildingDtoSchema))
88
+ dto: UpdateBuildingDto,
89
+ @CurrentUser() user: JwtPayload,
90
+ ) {
91
+ const building = await this.buildingService.update(id, dto, user.id);
92
+ return { data: building };
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### services/building.service.ts
98
+
99
+ ```typescript
100
+ import { Injectable } from '@nestjs/common';
101
+ import { SupabaseService } from '../../database/supabase.service';
102
+ import { LoggerService } from '../../logger/logger.service';
103
+ import { DomainException } from '../../common/exceptions/domain.exception';
104
+ import {
105
+ CreateBuildingDto,
106
+ } from '../dtos/create-building.dto';
107
+ import {
108
+ UpdateBuildingDto,
109
+ } from '../dtos/update-building.dto';
110
+ import { Building } from '../entities/building.entity';
111
+
112
+ @Injectable()
113
+ export class BuildingService {
114
+ constructor(
115
+ private readonly supabase: SupabaseService,
116
+ private readonly logger: LoggerService,
117
+ ) {}
118
+
119
+ async create(dto: CreateBuildingDto, userId: string): Promise<Building> {
120
+ // Validate business rules
121
+ const { count, error: countError } = await this.supabase
122
+ .from('buildings')
123
+ .select('id', { count: 'exact', head: true })
124
+ .eq('owner_id', userId);
125
+
126
+ if (countError) {
127
+ this.logger.error('Failed to count buildings', { error: countError });
128
+ throw new DomainException('INTERNAL_ERROR');
129
+ }
130
+
131
+ if (count >= 100) {
132
+ throw new DomainException('MAX_BUILDINGS_EXCEEDED', {
133
+ limit: 100,
134
+ current: count,
135
+ });
136
+ }
137
+
138
+ // Create record
139
+ const { data, error } = await this.supabase
140
+ .from('buildings')
141
+ .insert([
142
+ {
143
+ ...dto,
144
+ owner_id: userId,
145
+ },
146
+ ])
147
+ .select()
148
+ .single();
149
+
150
+ if (error) {
151
+ this.logger.error('Failed to create building', { error, dto, userId });
152
+ throw new DomainException('BUILDING_CREATE_FAILED', {
153
+ reason: error.message,
154
+ });
155
+ }
156
+
157
+ this.logger.info('Building created', { id: data.id, owner_id: userId });
158
+ return data;
159
+ }
160
+
161
+ async findByOwner(userId: string): Promise<Building[]> {
162
+ const { data, error } = await this.supabase
163
+ .from('buildings')
164
+ .select('*')
165
+ .eq('owner_id', userId)
166
+ .order('created_at', { ascending: false });
167
+
168
+ if (error) {
169
+ this.logger.error('Failed to fetch buildings', { error, userId });
170
+ throw new DomainException('INTERNAL_ERROR');
171
+ }
172
+
173
+ return data ?? [];
174
+ }
175
+
176
+ async findById(id: string, userId?: string): Promise<Building> {
177
+ const query = this.supabase
178
+ .from('buildings')
179
+ .select('*')
180
+ .eq('id', id);
181
+
182
+ if (userId) {
183
+ query.eq('owner_id', userId);
184
+ }
185
+
186
+ const { data, error } = await query.single();
187
+
188
+ if (error || !data) {
189
+ throw new DomainException('BUILDING_NOT_FOUND', { id });
190
+ }
191
+
192
+ return data;
193
+ }
194
+
195
+ async update(
196
+ id: string,
197
+ dto: UpdateBuildingDto,
198
+ userId: string,
199
+ ): Promise<Building> {
200
+ // Verify ownership
201
+ await this.findById(id, userId);
202
+
203
+ const { data, error } = await this.supabase
204
+ .from('buildings')
205
+ .update(dto)
206
+ .eq('id', id)
207
+ .eq('owner_id', userId)
208
+ .select()
209
+ .single();
210
+
211
+ if (error) {
212
+ this.logger.error('Failed to update building', { error, id, userId });
213
+ throw new DomainException('BUILDING_UPDATE_FAILED');
214
+ }
215
+
216
+ this.logger.info('Building updated', { id, owner_id: userId });
217
+ return data;
218
+ }
219
+ }
220
+ ```
221
+
222
+ ### dtos/create-building.dto.ts
223
+
224
+ ```typescript
225
+ import { z } from 'zod';
226
+
227
+ export const CreateBuildingDtoSchema = z.object({
228
+ nameAr: z.string().min(1).max(200),
229
+ nameEn: z.string().min(1).max(200),
230
+ buildingType: z.enum(['RESIDENTIAL', 'COMMERCIAL', 'MIXED']),
231
+ unitCount: z.number().int().positive().max(10000),
232
+ address: z.string().min(1).max(500).optional(),
233
+ latitude: z.number().min(-90).max(90).optional(),
234
+ longitude: z.number().min(-180).max(180).optional(),
235
+ });
236
+
237
+ export type CreateBuildingDto = z.infer<typeof CreateBuildingDtoSchema>;
238
+ ```
239
+
240
+ ### dtos/update-building.dto.ts
241
+
242
+ ```typescript
243
+ import { z } from 'zod';
244
+
245
+ export const UpdateBuildingDtoSchema = z.object({
246
+ nameAr: z.string().min(1).max(200).optional(),
247
+ nameEn: z.string().min(1).max(200).optional(),
248
+ buildingType: z.enum(['RESIDENTIAL', 'COMMERCIAL', 'MIXED']).optional(),
249
+ unitCount: z.number().int().positive().max(10000).optional(),
250
+ address: z.string().min(1).max(500).optional(),
251
+ latitude: z.number().min(-90).max(90).optional(),
252
+ longitude: z.number().min(-180).max(180).optional(),
253
+ });
254
+
255
+ export type UpdateBuildingDto = z.infer<typeof UpdateBuildingDtoSchema>;
256
+ ```
257
+
258
+ ### entities/building.entity.ts
259
+
260
+ ```typescript
261
+ export interface Building {
262
+ id: string;
263
+ owner_id: string;
264
+ nameAr: string;
265
+ nameEn: string;
266
+ buildingType: 'RESIDENTIAL' | 'COMMERCIAL' | 'MIXED';
267
+ unitCount: number;
268
+ address?: string;
269
+ latitude?: number;
270
+ longitude?: number;
271
+ created_at: string;
272
+ updated_at: string;
273
+ }
274
+ ```
275
+
276
+ ### __tests__/building.service.spec.ts
277
+
278
+ ```typescript
279
+ import { Test, TestingModule } from '@nestjs/testing';
280
+ import { BuildingService } from '../services/building.service';
281
+ import { SupabaseService } from '../../database/supabase.service';
282
+ import { LoggerService } from '../../logger/logger.service';
283
+ import { DomainException } from '../../common/exceptions/domain.exception';
284
+ import { CreateBuildingDto } from '../dtos/create-building.dto';
285
+
286
+ describe('BuildingService', () => {
287
+ let service: BuildingService;
288
+ let mockSupabase: jest.Mocked<SupabaseService>;
289
+ let mockLogger: jest.Mocked<LoggerService>;
290
+
291
+ beforeEach(async () => {
292
+ mockSupabase = {
293
+ from: jest.fn(),
294
+ } as any;
295
+
296
+ mockLogger = {
297
+ error: jest.fn(),
298
+ info: jest.fn(),
299
+ warn: jest.fn(),
300
+ debug: jest.fn(),
301
+ } as any;
302
+
303
+ const module: TestingModule = await Test.createTestingModule({
304
+ providers: [
305
+ BuildingService,
306
+ { provide: SupabaseService, useValue: mockSupabase },
307
+ { provide: LoggerService, useValue: mockLogger },
308
+ ],
309
+ }).compile();
310
+
311
+ service = module.get<BuildingService>(BuildingService);
312
+ });
313
+
314
+ describe('create', () => {
315
+ it('should create a building successfully', async () => {
316
+ const dto: CreateBuildingDto = {
317
+ nameAr: 'مبنى تجريبي',
318
+ nameEn: 'Test Building',
319
+ buildingType: 'RESIDENTIAL',
320
+ unitCount: 20,
321
+ address: '123 Main St',
322
+ };
323
+
324
+ const mockResult = {
325
+ id: '1',
326
+ owner_id: 'user-1',
327
+ ...dto,
328
+ created_at: new Date().toISOString(),
329
+ updated_at: new Date().toISOString(),
330
+ };
331
+
332
+ mockSupabase.from.mockReturnValue({
333
+ select: jest.fn().mockReturnThis(),
334
+ eq: jest.fn().mockReturnThis(),
335
+ count: jest.fn().mockResolvedValue({ count: 0, error: null }),
336
+ insert: jest.fn().mockReturnThis(),
337
+ single: jest.fn().mockResolvedValue({ data: mockResult, error: null }),
338
+ } as any);
339
+
340
+ const result = await service.create(dto, 'user-1');
341
+ expect(result.id).toBe('1');
342
+ expect(result.nameEn).toBe('Test Building');
343
+ });
344
+
345
+ it('should throw DomainException if max buildings exceeded', async () => {
346
+ const dto: CreateBuildingDto = {
347
+ nameAr: 'مبنى',
348
+ nameEn: 'Building',
349
+ buildingType: 'RESIDENTIAL',
350
+ unitCount: 10,
351
+ };
352
+
353
+ mockSupabase.from.mockReturnValue({
354
+ select: jest.fn().mockReturnThis(),
355
+ eq: jest.fn().mockReturnThis(),
356
+ count: jest.fn().mockResolvedValue({ count: 100, error: null }),
357
+ } as any);
358
+
359
+ await expect(service.create(dto, 'user-1')).rejects.toThrow(
360
+ DomainException,
361
+ );
362
+ });
363
+ });
364
+
365
+ describe('findById', () => {
366
+ it('should find a building by id', async () => {
367
+ const mockBuilding = { id: '1', nameEn: 'Test', owner_id: 'user-1' };
368
+
369
+ mockSupabase.from.mockReturnValue({
370
+ select: jest.fn().mockReturnThis(),
371
+ eq: jest.fn().mockReturnThis(),
372
+ single: jest.fn().mockResolvedValue({ data: mockBuilding, error: null }),
373
+ } as any);
374
+
375
+ const result = await service.findById('1', 'user-1');
376
+ expect(result.id).toBe('1');
377
+ });
378
+
379
+ it('should throw DomainException if building not found', async () => {
380
+ mockSupabase.from.mockReturnValue({
381
+ select: jest.fn().mockReturnThis(),
382
+ eq: jest.fn().mockReturnThis(),
383
+ single: jest
384
+ .fn()
385
+ .mockResolvedValue({ data: null, error: { message: 'Not found' } }),
386
+ } as any);
387
+
388
+ await expect(service.findById('999', 'user-1')).rejects.toThrow(
389
+ DomainException,
390
+ );
391
+ });
392
+ });
393
+ });
394
+ ```
395
+
396
+ ---
397
+
398
+ ## 2. Global Exception Filter
399
+
400
+ Centralized error handling that converts domain exceptions to HTTP responses.
401
+
402
+ ### common/filters/exception.filter.ts
403
+
404
+ ```typescript
405
+ import {
406
+ ExceptionFilter,
407
+ Catch,
408
+ ArgumentsHost,
409
+ HttpStatus,
410
+ BadRequestException,
411
+ } from '@nestjs/common';
412
+ import { Response } from 'express';
413
+ import { DomainException } from '../exceptions/domain.exception';
414
+ import { LoggerService } from '../../logger/logger.service';
415
+
416
+ interface ErrorResponse {
417
+ http_status: number;
418
+ error_code: string;
419
+ message_key: string;
420
+ request_id: string;
421
+ details?: Record<string, any>;
422
+ timestamp: string;
423
+ }
424
+
425
+ @Catch()
426
+ export class GlobalExceptionFilter implements ExceptionFilter {
427
+ constructor(private readonly logger: LoggerService) {}
428
+
429
+ catch(exception: unknown, host: ArgumentsHost) {
430
+ const ctx = host.switchToHttp();
431
+ const response = ctx.getResponse<Response>();
432
+ const request = ctx.getRequest();
433
+ const requestId = request.id || 'unknown';
434
+
435
+ let statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
436
+ let errorCode = 'INTERNAL_ERROR';
437
+ let messageKey = 'error.internal_error';
438
+ let details: Record<string, any> | undefined;
439
+
440
+ // Handle domain exceptions
441
+ if (exception instanceof DomainException) {
442
+ ({ statusCode, errorCode, messageKey, details } =
443
+ this.handleDomainException(exception));
444
+ this.logger.warn('Domain exception', {
445
+ errorCode,
446
+ details,
447
+ requestId,
448
+ path: request.path,
449
+ });
450
+ }
451
+ // Handle validation errors (Zod)
452
+ else if (exception instanceof BadRequestException) {
453
+ statusCode = HttpStatus.BAD_REQUEST;
454
+ errorCode = 'VALIDATION_FAILED';
455
+ messageKey = 'error.validation_failed';
456
+ const errorResponse = exception.getResponse();
457
+ if (typeof errorResponse === 'object' && 'message' in errorResponse) {
458
+ details = (errorResponse as any).message;
459
+ }
460
+ this.logger.warn('Validation error', {
461
+ details,
462
+ requestId,
463
+ path: request.path,
464
+ });
465
+ }
466
+ // Handle generic errors
467
+ else {
468
+ this.logger.error('Unhandled exception', {
469
+ error: exception instanceof Error ? exception.message : String(exception),
470
+ stack: exception instanceof Error ? exception.stack : undefined,
471
+ requestId,
472
+ path: request.path,
473
+ });
474
+ }
475
+
476
+ const errorResponse: ErrorResponse = {
477
+ http_status: statusCode,
478
+ error_code: errorCode,
479
+ message_key: messageKey,
480
+ request_id: requestId,
481
+ ...(details && { details }),
482
+ timestamp: new Date().toISOString(),
483
+ };
484
+
485
+ response.status(statusCode).json(errorResponse);
486
+ }
487
+
488
+ private handleDomainException(
489
+ exception: DomainException,
490
+ ): {
491
+ statusCode: number;
492
+ errorCode: string;
493
+ messageKey: string;
494
+ details?: Record<string, any>;
495
+ } {
496
+ const statusMap: Record<string, number> = {
497
+ NOT_FOUND: HttpStatus.NOT_FOUND,
498
+ UNAUTHORIZED: HttpStatus.UNAUTHORIZED,
499
+ FORBIDDEN: HttpStatus.FORBIDDEN,
500
+ VALIDATION_FAILED: HttpStatus.BAD_REQUEST,
501
+ CONFLICT: HttpStatus.CONFLICT,
502
+ INTERNAL_ERROR: HttpStatus.INTERNAL_SERVER_ERROR,
503
+ MAX_BUILDINGS_EXCEEDED: HttpStatus.BAD_REQUEST,
504
+ BUILDING_CREATE_FAILED: HttpStatus.BAD_REQUEST,
505
+ BUILDING_UPDATE_FAILED: HttpStatus.BAD_REQUEST,
506
+ BUILDING_NOT_FOUND: HttpStatus.NOT_FOUND,
507
+ };
508
+
509
+ const statusCode = statusMap[exception.code] || HttpStatus.INTERNAL_SERVER_ERROR;
510
+ const messageKey = `error.${exception.code.toLowerCase()}`;
511
+
512
+ return {
513
+ statusCode,
514
+ errorCode: exception.code,
515
+ messageKey,
516
+ details: exception.details,
517
+ };
518
+ }
519
+ }
520
+ ```
521
+
522
+ ### common/exceptions/domain.exception.ts
523
+
524
+ ```typescript
525
+ export class DomainException extends Error {
526
+ constructor(
527
+ public readonly code: string,
528
+ public readonly details?: Record<string, any>,
529
+ ) {
530
+ super(`Domain error: ${code}`);
531
+ this.name = 'DomainException';
532
+ }
533
+ }
534
+ ```
535
+
536
+ ### app.module.ts (register filter)
537
+
538
+ ```typescript
539
+ import { APP_FILTER } from '@nestjs/core';
540
+ import { GlobalExceptionFilter } from './common/filters/exception.filter';
541
+ import { LoggerService } from './logger/logger.service';
542
+
543
+ @Module({
544
+ imports: [/* ... */],
545
+ controllers: [AppController],
546
+ providers: [
547
+ {
548
+ provide: APP_FILTER,
549
+ useClass: GlobalExceptionFilter,
550
+ },
551
+ LoggerService,
552
+ ],
553
+ })
554
+ export class AppModule {}
555
+ ```
556
+
557
+ ---
558
+
559
+ ## 3. Auth Guard with JWT Validation
560
+
561
+ Validates JWT token and attaches user to request.
562
+
563
+ ### common/guards/auth.guard.ts
564
+
565
+ ```typescript
566
+ import {
567
+ Injectable,
568
+ CanActivate,
569
+ ExecutionContext,
570
+ UnauthorizedException,
571
+ } from '@nestjs/common';
572
+ import { ConfigService } from '@nestjs/config';
573
+ import { Request } from 'express';
574
+ import * as jwt from 'jsonwebtoken';
575
+ import { JwtPayload } from '../types/jwt.types';
576
+
577
+ @Injectable()
578
+ export class AuthGuard implements CanActivate {
579
+ constructor(private readonly configService: ConfigService) {}
580
+
581
+ canActivate(context: ExecutionContext): boolean {
582
+ const request = context.switchToHttp().getRequest<Request>();
583
+ const authHeader = request.headers.authorization;
584
+
585
+ if (!authHeader) {
586
+ throw new UnauthorizedException('Missing authorization header');
587
+ }
588
+
589
+ const [scheme, token] = authHeader.split(' ');
590
+
591
+ if (scheme !== 'Bearer') {
592
+ throw new UnauthorizedException('Invalid authorization scheme');
593
+ }
594
+
595
+ if (!token) {
596
+ throw new UnauthorizedException('Missing bearer token');
597
+ }
598
+
599
+ try {
600
+ const secret = this.configService.get<string>('JWT_SECRET');
601
+ const payload = jwt.verify(token, secret) as JwtPayload;
602
+
603
+ request.user = payload;
604
+ request.id = request.headers['x-request-id'] as string;
605
+ return true;
606
+ } catch (error) {
607
+ if (error instanceof jwt.TokenExpiredError) {
608
+ throw new UnauthorizedException('Token expired');
609
+ }
610
+ if (error instanceof jwt.JsonWebTokenError) {
611
+ throw new UnauthorizedException('Invalid token');
612
+ }
613
+ throw new UnauthorizedException('Unauthorized');
614
+ }
615
+ }
616
+ }
617
+ ```
618
+
619
+ ### common/types/jwt.types.ts
620
+
621
+ ```typescript
622
+ export interface JwtPayload {
623
+ sub: string; // user ID
624
+ id: string; // user ID (duplicate for compatibility)
625
+ email: string;
626
+ role: 'USER' | 'ADMIN';
627
+ iat: number;
628
+ exp: number;
629
+ }
630
+
631
+ declare global {
632
+ namespace Express {
633
+ interface Request {
634
+ user?: JwtPayload;
635
+ id?: string; // request ID
636
+ }
637
+ }
638
+ }
639
+ ```
640
+
641
+ ---
642
+
643
+ ## 4. Roles Guard with Decorator
644
+
645
+ Checks user role against @Roles() decorator on method.
646
+
647
+ ### common/guards/roles.guard.ts
648
+
649
+ ```typescript
650
+ import {
651
+ Injectable,
652
+ CanActivate,
653
+ ExecutionContext,
654
+ ForbiddenException,
655
+ } from '@nestjs/common';
656
+ import { Reflector } from '@nestjs/core';
657
+ import { Request } from 'express';
658
+
659
+ @Injectable()
660
+ export class RolesGuard implements CanActivate {
661
+ constructor(private readonly reflector: Reflector) {}
662
+
663
+ canActivate(context: ExecutionContext): boolean {
664
+ const requiredRoles = this.reflector.get<string[]>(
665
+ 'roles',
666
+ context.getHandler(),
667
+ );
668
+
669
+ if (!requiredRoles || requiredRoles.length === 0) {
670
+ return true;
671
+ }
672
+
673
+ const request = context.switchToHttp().getRequest<Request>();
674
+ const user = request.user;
675
+
676
+ if (!user) {
677
+ throw new ForbiddenException('User not authenticated');
678
+ }
679
+
680
+ if (!requiredRoles.includes(user.role)) {
681
+ throw new ForbiddenException(
682
+ `Insufficient permissions. Required roles: ${requiredRoles.join(', ')}`,
683
+ );
684
+ }
685
+
686
+ return true;
687
+ }
688
+ }
689
+ ```
690
+
691
+ ### common/decorators/roles.decorator.ts
692
+
693
+ ```typescript
694
+ import { SetMetadata } from '@nestjs/common';
695
+
696
+ export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
697
+ ```
698
+
699
+ ### Usage in controller
700
+
701
+ ```typescript
702
+ @Post()
703
+ @UseGuards(AuthGuard, RolesGuard)
704
+ @Roles('ADMIN', 'MANAGER')
705
+ async create(@Body() dto: CreateDto) {
706
+ // Only authenticated admins/managers reach here
707
+ }
708
+ ```
709
+
710
+ ---
711
+
712
+ ## 5. Request ID Interceptor
713
+
714
+ Generates or propagates a unique request_id UUID for tracing.
715
+
716
+ ### common/interceptors/request-id.interceptor.ts
717
+
718
+ ```typescript
719
+ import {
720
+ Injectable,
721
+ NestInterceptor,
722
+ ExecutionContext,
723
+ CallHandler,
724
+ } from '@nestjs/common';
725
+ import { Observable } from 'rxjs';
726
+ import { Request, Response } from 'express';
727
+ import { v4 as uuidv4 } from 'uuid';
728
+
729
+ @Injectable()
730
+ export class RequestIdInterceptor implements NestInterceptor {
731
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
732
+ const request = context.switchToHttp().getRequest<Request>();
733
+ const response = context.switchToHttp().getResponse<Response>();
734
+
735
+ // Use existing request ID or generate new one
736
+ const requestId =
737
+ (request.headers['x-request-id'] as string) || uuidv4();
738
+
739
+ request.id = requestId;
740
+ response.setHeader('x-request-id', requestId);
741
+
742
+ return next.handle();
743
+ }
744
+ }
745
+ ```
746
+
747
+ ### app.module.ts (register interceptor)
748
+
749
+ ```typescript
750
+ import { APP_INTERCEPTOR } from '@nestjs/core';
751
+ import { RequestIdInterceptor } from './common/interceptors/request-id.interceptor';
752
+
753
+ @Module({
754
+ providers: [
755
+ {
756
+ provide: APP_INTERCEPTOR,
757
+ useClass: RequestIdInterceptor,
758
+ },
759
+ ],
760
+ })
761
+ export class AppModule {}
762
+ ```
763
+
764
+ ---
765
+
766
+ ## 6. Zod Validation Pipe
767
+
768
+ Validates request body against Zod schema.
769
+
770
+ ### common/pipes/zod-validation.pipe.ts
771
+
772
+ ```typescript
773
+ import {
774
+ PipeTransform,
775
+ Injectable,
776
+ BadRequestException,
777
+ ArgumentMetadata,
778
+ } from '@nestjs/common';
779
+ import { ZodSchema, ZodError } from 'zod';
780
+
781
+ @Injectable()
782
+ export class ZodValidationPipe implements PipeTransform {
783
+ constructor(private schema: ZodSchema) {}
784
+
785
+ transform(value: any, metadata: ArgumentMetadata) {
786
+ try {
787
+ const parsed = this.schema.parse(value);
788
+ return parsed;
789
+ } catch (error) {
790
+ if (error instanceof ZodError) {
791
+ const formatted = error.errors.map((err) => ({
792
+ field: err.path.join('.'),
793
+ message: err.message,
794
+ code: err.code,
795
+ }));
796
+
797
+ throw new BadRequestException({
798
+ message: 'Validation failed',
799
+ errors: formatted,
800
+ });
801
+ }
802
+ throw error;
803
+ }
804
+ }
805
+ }
806
+ ```
807
+
808
+ ### Usage in controller
809
+
810
+ ```typescript
811
+ @Post()
812
+ async create(
813
+ @Body(new ZodValidationPipe(CreateBuildingDtoSchema))
814
+ dto: CreateBuildingDto,
815
+ ) {
816
+ return { data: await this.buildingService.create(dto) };
817
+ }
818
+ ```
819
+
820
+ ---
821
+
822
+ ## 7. Supabase Database Service
823
+
824
+ Wrapper around Supabase client with RLS and transaction support.
825
+
826
+ ### database/supabase.service.ts
827
+
828
+ ```typescript
829
+ import { Injectable } from '@nestjs/common';
830
+ import { ConfigService } from '@nestjs/config';
831
+ import { createClient, SupabaseClient } from '@supabase/supabase-js';
832
+ import { LoggerService } from '../logger/logger.service';
833
+
834
+ @Injectable()
835
+ export class SupabaseService {
836
+ private client: SupabaseClient;
837
+
838
+ constructor(
839
+ private readonly configService: ConfigService,
840
+ private readonly logger: LoggerService,
841
+ ) {
842
+ const url = this.configService.get<string>('SUPABASE_URL');
843
+ const key = this.configService.get<string>('SUPABASE_ANON_KEY');
844
+
845
+ if (!url || !key) {
846
+ throw new Error('Missing Supabase configuration');
847
+ }
848
+
849
+ this.client = createClient(url, key);
850
+ }
851
+
852
+ /**
853
+ * Get typed access to a table
854
+ * Usage: this.supabase.from('buildings').select('*')
855
+ */
856
+ from<T = any>(table: string) {
857
+ return this.client.from<T>(table);
858
+ }
859
+
860
+ /**
861
+ * Execute a stored procedure (for transactions)
862
+ * Usage: this.supabase.rpc('my_function', { arg1: 'value' })
863
+ */
864
+ rpc(name: string, params?: Record<string, any>) {
865
+ return this.client.rpc(name, params);
866
+ }
867
+
868
+ /**
869
+ * Direct access to client for advanced operations
870
+ */
871
+ getClient() {
872
+ return this.client;
873
+ }
874
+
875
+ /**
876
+ * Health check
877
+ */
878
+ async healthCheck(): Promise<boolean> {
879
+ try {
880
+ const { error } = await this.client.from('_supabase_migrations').select('id').limit(1);
881
+ if (error) {
882
+ this.logger.warn('Supabase health check failed', { error });
883
+ return false;
884
+ }
885
+ return true;
886
+ } catch (error) {
887
+ this.logger.error('Supabase health check exception', { error });
888
+ return false;
889
+ }
890
+ }
891
+ }
892
+ ```
893
+
894
+ ### Usage in services
895
+
896
+ ```typescript
897
+ // Select query
898
+ const { data, error } = await this.supabase
899
+ .from('buildings')
900
+ .select('*')
901
+ .eq('owner_id', userId);
902
+
903
+ // Insert
904
+ const { data, error } = await this.supabase
905
+ .from('buildings')
906
+ .insert([{ nameEn: 'Test', owner_id: userId }])
907
+ .select()
908
+ .single();
909
+
910
+ // Update
911
+ const { data, error } = await this.supabase
912
+ .from('buildings')
913
+ .update({ nameEn: 'Updated' })
914
+ .eq('id', buildingId)
915
+ .select()
916
+ .single();
917
+
918
+ // Transaction via stored procedure
919
+ const { data, error } = await this.supabase.rpc('create_building_with_units', {
920
+ p_owner_id: userId,
921
+ p_name_en: 'Test',
922
+ p_unit_count: 10,
923
+ });
924
+ ```
925
+
926
+ ---
927
+
928
+ ## 8. Config Module with Zod Validation
929
+
930
+ Centralized configuration with startup validation.
931
+
932
+ ### config/configuration.ts
933
+
934
+ ```typescript
935
+ import { z } from 'zod';
936
+
937
+ const envSchema = z.object({
938
+ // Server
939
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
940
+ PORT: z.string().pipe(z.coerce.number()).default('3000'),
941
+
942
+ // Database
943
+ SUPABASE_URL: z.string().url(),
944
+ SUPABASE_ANON_KEY: z.string(),
945
+ SUPABASE_SERVICE_ROLE_KEY: z.string().optional(),
946
+
947
+ // JWT
948
+ JWT_SECRET: z.string().min(32),
949
+ JWT_EXPIRY: z.string().default('24h'),
950
+
951
+ // Logging
952
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
953
+
954
+ // CORS
955
+ CORS_ORIGIN: z.string().default('*'),
956
+
957
+ // Features
958
+ FEATURE_BUILDING_VERIFICATION: z
959
+ .string()
960
+ .transform((v) => v === 'true')
961
+ .default('true'),
962
+ });
963
+
964
+ export type Environment = z.infer<typeof envSchema>;
965
+
966
+ export const validateConfig = (config: Record<string, unknown>): Environment => {
967
+ try {
968
+ return envSchema.parse(config);
969
+ } catch (error) {
970
+ if (error instanceof z.ZodError) {
971
+ const formatted = error.errors
972
+ .map((err) => `${err.path.join('.')}: ${err.message}`)
973
+ .join(', ');
974
+ throw new Error(`Configuration validation failed: ${formatted}`);
975
+ }
976
+ throw error;
977
+ }
978
+ };
979
+
980
+ export default () => ({
981
+ nodeEnv: process.env.NODE_ENV,
982
+ port: parseInt(process.env.PORT || '3000', 10),
983
+ supabase: {
984
+ url: process.env.SUPABASE_URL,
985
+ anonKey: process.env.SUPABASE_ANON_KEY,
986
+ serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
987
+ },
988
+ jwt: {
989
+ secret: process.env.JWT_SECRET,
990
+ expiry: process.env.JWT_EXPIRY,
991
+ },
992
+ logging: {
993
+ level: process.env.LOG_LEVEL,
994
+ },
995
+ cors: {
996
+ origin: process.env.CORS_ORIGIN,
997
+ },
998
+ features: {
999
+ buildingVerification: process.env.FEATURE_BUILDING_VERIFICATION === 'true',
1000
+ },
1001
+ });
1002
+ ```
1003
+
1004
+ ### config/config.module.ts
1005
+
1006
+ ```typescript
1007
+ import { Module } from '@nestjs/common';
1008
+ import { ConfigModule as NestConfigModule } from '@nestjs/config';
1009
+ import configuration, { validateConfig } from './configuration';
1010
+
1011
+ @Module({
1012
+ imports: [
1013
+ NestConfigModule.forRoot({
1014
+ load: [configuration],
1015
+ validate: validateConfig,
1016
+ isGlobal: true,
1017
+ expandVariables: true,
1018
+ }),
1019
+ ],
1020
+ })
1021
+ export class ConfigModule {}
1022
+ ```
1023
+
1024
+ ### app.module.ts (import config)
1025
+
1026
+ ```typescript
1027
+ import { Module } from '@nestjs/common';
1028
+ import { ConfigModule } from './config/config.module';
1029
+ import { BuildingModule } from './modules/building/building.module';
1030
+
1031
+ @Module({
1032
+ imports: [ConfigModule, BuildingModule],
1033
+ })
1034
+ export class AppModule {}
1035
+ ```
1036
+
1037
+ ### Usage in services
1038
+
1039
+ ```typescript
1040
+ import { ConfigService } from '@nestjs/config';
1041
+ import { Environment } from '../config/configuration';
1042
+
1043
+ constructor(private readonly configService: ConfigService<Environment>) {}
1044
+
1045
+ someMethod() {
1046
+ const secret = this.configService.get('JWT_SECRET');
1047
+ const supabaseUrl = this.configService.get('SUPABASE_URL');
1048
+ const isFeatureEnabled = this.configService.get('FEATURE_BUILDING_VERIFICATION');
1049
+ }
1050
+ ```
1051
+
1052
+ ---
1053
+
1054
+ ## 9. Current User Decorator
1055
+
1056
+ Extracts authenticated user from request.
1057
+
1058
+ ### common/decorators/current-user.decorator.ts
1059
+
1060
+ ```typescript
1061
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
1062
+ import { Request } from 'express';
1063
+ import { JwtPayload } from '../types/jwt.types';
1064
+
1065
+ export const CurrentUser = createParamDecorator(
1066
+ (data: unknown, ctx: ExecutionContext): JwtPayload | undefined => {
1067
+ const request = ctx.switchToHttp().getRequest<Request>();
1068
+ return request.user;
1069
+ },
1070
+ );
1071
+ ```
1072
+
1073
+ ### Usage in controller
1074
+
1075
+ ```typescript
1076
+ @Get()
1077
+ async getProfile(@CurrentUser() user: JwtPayload) {
1078
+ return { data: { id: user.id, email: user.email } };
1079
+ }
1080
+ ```
1081
+
1082
+ ---
1083
+
1084
+ ## 10. Logging Service
1085
+
1086
+ Structured logging with request tracing.
1087
+
1088
+ ### logger/logger.service.ts
1089
+
1090
+ ```typescript
1091
+ import { Injectable } from '@nestjs/common';
1092
+ import { ConfigService } from '@nestjs/config';
1093
+
1094
+ export enum LogLevel {
1095
+ DEBUG = 'debug',
1096
+ INFO = 'info',
1097
+ WARN = 'warn',
1098
+ ERROR = 'error',
1099
+ }
1100
+
1101
+ @Injectable()
1102
+ export class LoggerService {
1103
+ private logLevel: LogLevel = LogLevel.INFO;
1104
+
1105
+ constructor(private readonly configService: ConfigService) {
1106
+ const level = this.configService.get<string>('LOG_LEVEL');
1107
+ if (level) {
1108
+ this.logLevel = level as LogLevel;
1109
+ }
1110
+ }
1111
+
1112
+ private shouldLog(level: LogLevel): boolean {
1113
+ const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
1114
+ return levels.indexOf(level) >= levels.indexOf(this.logLevel);
1115
+ }
1116
+
1117
+ debug(message: string, context?: Record<string, any>) {
1118
+ if (this.shouldLog(LogLevel.DEBUG)) {
1119
+ console.debug(
1120
+ JSON.stringify({
1121
+ level: 'debug',
1122
+ timestamp: new Date().toISOString(),
1123
+ message,
1124
+ ...context,
1125
+ }),
1126
+ );
1127
+ }
1128
+ }
1129
+
1130
+ info(message: string, context?: Record<string, any>) {
1131
+ if (this.shouldLog(LogLevel.INFO)) {
1132
+ console.log(
1133
+ JSON.stringify({
1134
+ level: 'info',
1135
+ timestamp: new Date().toISOString(),
1136
+ message,
1137
+ ...context,
1138
+ }),
1139
+ );
1140
+ }
1141
+ }
1142
+
1143
+ warn(message: string, context?: Record<string, any>) {
1144
+ if (this.shouldLog(LogLevel.WARN)) {
1145
+ console.warn(
1146
+ JSON.stringify({
1147
+ level: 'warn',
1148
+ timestamp: new Date().toISOString(),
1149
+ message,
1150
+ ...context,
1151
+ }),
1152
+ );
1153
+ }
1154
+ }
1155
+
1156
+ error(message: string, context?: Record<string, any>) {
1157
+ if (this.shouldLog(LogLevel.ERROR)) {
1158
+ console.error(
1159
+ JSON.stringify({
1160
+ level: 'error',
1161
+ timestamp: new Date().toISOString(),
1162
+ message,
1163
+ ...context,
1164
+ }),
1165
+ );
1166
+ }
1167
+ }
1168
+ }
1169
+ ```
1170
+
1171
+ ---
1172
+
1173
+ All templates are production-ready and follow NestJS best practices. Adapt to your specific needs while maintaining the architectural patterns.