@stonepandastudio/cairn 0.4.1 → 0.5.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 (68) hide show
  1. package/README.md +33 -22
  2. package/bin/cairn.js +5 -0
  3. package/lib/init.js +58 -14
  4. package/lib/render/cli.js +106 -0
  5. package/lib/render/engine.js +148 -0
  6. package/lib/render/index.js +210 -0
  7. package/package.json +2 -1
  8. package/presets/EXTRACTION.md +210 -0
  9. package/presets/README.md +86 -0
  10. package/presets/angular/code-guidelines.md +197 -0
  11. package/presets/angular/slots/architect-discussion-topics.md +10 -0
  12. package/presets/angular/slots/architect-mandatory-docs.md +5 -0
  13. package/presets/angular/slots/architect-references.md +4 -0
  14. package/presets/angular/slots/implementation-reference.md +34 -0
  15. package/presets/angular/slots/key-patterns.md +11 -0
  16. package/presets/angular/slots/plan-step-ordering.md +11 -0
  17. package/presets/angular/slots/review-checklist.md +16 -0
  18. package/presets/angular/variants/i18n-external-service.md +11 -0
  19. package/presets/angular/variants/i18n-glossr.md +63 -0
  20. package/presets/core/AGENTS.md +49 -0
  21. package/presets/core/README.md +35 -0
  22. package/presets/core/WORKFLOW.md +56 -0
  23. package/presets/core/agents/architect.md +269 -0
  24. package/presets/core/agents/developer.md +145 -0
  25. package/presets/core/agents/reviewer.md +167 -0
  26. package/presets/core/commands/_stub.md +7 -0
  27. package/presets/core/workflow.json +45 -0
  28. package/presets/drizzle/code-guidelines.md +33 -0
  29. package/presets/drizzle/slots/architect-discussion-topics.md +4 -0
  30. package/presets/drizzle/slots/architect-mandatory-docs.md +4 -0
  31. package/presets/drizzle/slots/implementation-reference.md +17 -0
  32. package/presets/drizzle/slots/key-patterns.md +7 -0
  33. package/presets/drizzle/slots/review-checklist.md +10 -0
  34. package/presets/nestjs/code-guidelines.md +273 -0
  35. package/presets/nestjs/slots/architect-discussion-topics.md +4 -0
  36. package/presets/nestjs/slots/architect-mandatory-docs.md +5 -0
  37. package/presets/nestjs/slots/architect-references.md +5 -0
  38. package/presets/nestjs/slots/implementation-reference.md +45 -0
  39. package/presets/nestjs/slots/key-patterns.md +11 -0
  40. package/presets/nestjs/slots/plan-step-ordering.md +12 -0
  41. package/presets/nestjs/slots/review-checklist.md +12 -0
  42. package/presets/nestjs/variants/validation-class-validator.md +120 -0
  43. package/presets/nestjs/variants/validation-zod.md +194 -0
  44. package/presets/nextjs/code-guidelines.md +45 -0
  45. package/presets/nextjs/slots/architect-discussion-topics.md +5 -0
  46. package/presets/nextjs/slots/architect-mandatory-docs.md +3 -0
  47. package/presets/nextjs/slots/architect-references.md +6 -0
  48. package/presets/nextjs/slots/implementation-reference.md +24 -0
  49. package/presets/nextjs/slots/key-patterns.md +8 -0
  50. package/presets/nextjs/slots/plan-step-ordering.md +11 -0
  51. package/presets/nextjs/slots/review-checklist.md +11 -0
  52. package/presets/react/code-guidelines.md +46 -0
  53. package/presets/react/slots/architect-discussion-topics.md +5 -0
  54. package/presets/react/slots/architect-references.md +5 -0
  55. package/presets/react/slots/implementation-reference.md +26 -0
  56. package/presets/react/slots/key-patterns.md +8 -0
  57. package/presets/react/slots/plan-step-ordering.md +9 -0
  58. package/presets/react/slots/review-checklist.md +10 -0
  59. package/presets/tailwind/code-guidelines.md +28 -0
  60. package/presets/tailwind/slots/implementation-reference.md +8 -0
  61. package/presets/tailwind/slots/key-patterns.md +5 -0
  62. package/presets/tailwind/slots/review-checklist.md +8 -0
  63. package/presets/typeorm/code-guidelines.md +329 -0
  64. package/presets/typeorm/slots/architect-discussion-topics.md +4 -0
  65. package/presets/typeorm/slots/architect-mandatory-docs.md +3 -0
  66. package/presets/typeorm/slots/implementation-reference.md +19 -0
  67. package/presets/typeorm/slots/key-patterns.md +8 -0
  68. package/presets/typeorm/slots/review-checklist.md +8 -0
@@ -0,0 +1,4 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's
2
+ architect-mandatory-docs slot on `--stack <framework>,drizzle`. -->
3
+ - `ai/infrastructure/DATABASE_SCHEMA.md`
4
+ - `lib/db/schema.ts` — the current Drizzle schema
@@ -0,0 +1,17 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's
2
+ implementation-reference slot on `--stack <framework>,drizzle`. -->
3
+
4
+ ### Schema
5
+ - All tables in `lib/db/schema.ts`, Drizzle syntax; snake_case columns, camelCase fields
6
+ - Relations declared with `relations(...)` so typed joins work
7
+ - No RLS in the schema module — that goes in the migration
8
+
9
+ ### Migrations
10
+ - `pnpm exec drizzle-kit generate` for mechanical diffs
11
+ - Hand-write the `.sql` only for RLS policies, functions, triggers, backfills
12
+ - One commit = schema edit + its migration
13
+
14
+ ### Access
15
+ - Server: the Drizzle client in `lib/db/client.ts` (service-role, never in `"use client"`)
16
+ - Browser: the Supabase client only, bounded by RLS
17
+ - Reads select named columns; multi-step writes go in `db.transaction(...)`
@@ -0,0 +1,7 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's key-patterns slot
2
+ on `--stack <framework>,drizzle`. -->
3
+
4
+ Database schema: **`lib/db/schema.ts`** (single Drizzle module) and
5
+ **`ai/infrastructure/DATABASE_SCHEMA.md`** — keep both current with every migration.
6
+
7
+ Migration and RLS conventions: **`ai/infrastructure/code-guidelines.md`** § Drizzle.
@@ -0,0 +1,10 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's review-checklist
2
+ slot on `--stack <framework>,drizzle`. -->
3
+
4
+ | No raw SQL in application code — only the Drizzle query builder, or `sql` for expressions it can't model | any `.ts` outside `supabase/migrations/` |
5
+ | Schema change committed together with its `drizzle-kit` migration | `lib/db/schema.ts` + `supabase/migrations/` |
6
+ | Generated migration reviewed — no accidental drop or type change | the migration `.sql` |
7
+ | RLS policy declared in a migration, not assumed | `supabase/migrations/` |
8
+ | Server Drizzle client not imported from a `"use client"` module | client components |
9
+ | Multi-step write wrapped in `db.transaction(...)` | service / action files |
10
+ | `ai/infrastructure/DATABASE_SCHEMA.md` updated to match | that file |
@@ -0,0 +1,273 @@
1
+ # Code Guidelines
2
+
3
+ <!-- cairn preset: nestjs. Composes with presets/typeorm for TypeORM-backed services. -->
4
+
5
+ ## General TypeScript Guidelines
6
+
7
+ * Use TypeScript strict mode
8
+ * Prefer `const` over `let`, never use `var`
9
+ * Avoid using `any` type - use proper TypeScript types
10
+ * Use async/await instead of raw promises
11
+ * Handle all error cases explicitly
12
+ * Use proper error types from NestJS (`NotFoundException`, `BadRequestException`, etc.)
13
+
14
+ ## NestJS Module Structure
15
+
16
+ * Separate business logic (feature modules in `src/feature/`) from API layer (API modules in `src/api/modules/`)
17
+ * Use proper dependency injection with `@Injectable()`, `@Module()` decorators
18
+ * Register providers, imports, and exports correctly in module files
19
+ * Keep controllers thin - delegate business logic to services
20
+
21
+ ## File Naming Conventions
22
+
23
+ * Use kebab-case (dash-case) for all file names
24
+ * Modules: `feature-name.module.ts`
25
+ * Services: `feature-name.service.ts`
26
+ * Controllers: `feature-name.controller.ts`
27
+ * Entities: `feature-name.entity.ts`
28
+ * Interfaces: `interface-name.interface.ts` (stored in `interfaces/` folder)
29
+ * Tests: `feature-name.service.spec.ts`, `feature-name.controller.spec.ts`
30
+
31
+ DTO file naming follows the validation stack:
32
+
33
+ {{> slot: dto-naming }}
34
+
35
+ ## File Organization
36
+
37
+ Every utility function, model, constant, DTO, or mapping function should be in a separated file.
38
+
39
+ Files should be placed in appropriate folders within modules (see `ai/infrastructure/project-structure.md` for module details):
40
+
41
+ * **constants/** - Constants specific to the module
42
+ * **enums/** - Enums specific to the module
43
+ * **interfaces/** - TypeScript interfaces (one per file)
44
+ * **dtos/** - Data Transfer Objects for request/response
45
+ * **entities/** - Database entities
46
+ * **services/** - Business logic services
47
+ * **controllers/** - HTTP controllers
48
+ * **mappers/** - Mapping functions for transforming data
49
+
50
+ ## Interfaces
51
+
52
+ **IMPORTANT**: Store interfaces separately from code in dedicated interface files.
53
+
54
+ **Rules**:
55
+ * Each interface gets its own file in `src/feature/[feature-name]/interfaces/` folder
56
+ * File naming: convert interface name to kebab-case + `.interface.ts`
57
+ - E.g., `CreateUserData` → `create-user-data.interface.ts`
58
+ - E.g., `RegisterOwnerResult` → `register-owner-result.interface.ts`
59
+ * One interface per file — no mixing multiple interfaces
60
+ * Interfaces are NOT exported from barrel files unless necessary
61
+
62
+ **Examples**:
63
+ ```typescript
64
+ // src/feature/user/interfaces/create-user-data.interface.ts
65
+ export interface CreateUserData {
66
+ companyId: string;
67
+ email: string;
68
+ name: string;
69
+ passwordHash: string;
70
+ role?: TenantRole;
71
+ }
72
+
73
+ // src/feature/user/interfaces/register-owner-result.interface.ts
74
+ export interface RegisterOwnerResult {
75
+ user: User;
76
+ company: Company;
77
+ }
78
+ ```
79
+
80
+ ## Controllers
81
+
82
+ * Keep controllers thin - only handle HTTP concerns
83
+ * Use proper HTTP method decorators: `@Get()`, `@Post()`, `@Put()`, `@Delete()`, `@Patch()`
84
+ * Use parameter decorators: `@Body()`, `@Param()`, `@Query()`, `@Headers()`
85
+ * Use `@UseGuards()` for authentication and authorization
86
+ * Return consistent response structures
87
+ * Let NestJS exception filters handle errors
88
+
89
+ Response typing and serialization are stack-specific:
90
+
91
+ {{> slot: controller-response }}
92
+
93
+ ## Services
94
+
95
+ * Implement all business logic in services
96
+ * Use dependency injection for all dependencies
97
+ * Mark services with `@Injectable()` decorator
98
+ * Handle database transactions properly
99
+ * Throw appropriate NestJS exceptions
100
+ * Keep methods focused and single-purpose
101
+ * Use async/await for all asynchronous operations
102
+
103
+ ## DTOs (Data Transfer Objects)
104
+
105
+ * Create separate DTOs for different operations:
106
+ - `create-*.dto.ts` for POST requests
107
+ - `update-*.dto.ts` for PUT/PATCH requests
108
+ - `*-response.dto.ts` for responses
109
+ - `*-query.dto.ts` for query parameters
110
+
111
+ {{> slot: dto-definition }}
112
+
113
+ ## Validation
114
+
115
+ * Validate at API boundaries (in controllers via DTOs)
116
+ * Provide clear, user-friendly validation error messages
117
+
118
+ {{> slot: validation-stack }}
119
+
120
+ ## Error Handling
121
+
122
+ * Use NestJS built-in exceptions:
123
+ - `NotFoundException` - Resource not found (404)
124
+ - `BadRequestException` - Invalid input (400)
125
+ - `UnauthorizedException` - Not authenticated (401)
126
+ - `ForbiddenException` - Not authorized (403)
127
+ - `ConflictException` - Resource conflict (409)
128
+ * Create custom exception classes for domain-specific errors
129
+ * Use exception filters for global error handling
130
+ * Log errors appropriately (use NestJS Logger)
131
+ * Never expose sensitive information in error messages
132
+ * Never expose stack traces in production
133
+
134
+ ## Error Codes
135
+
136
+ **MANDATORY**: Every thrown exception **must** include a machine-readable `code` from `ERROR_CODES`.
137
+
138
+ **Why**: Frontend uses `code` to display localized messages. String-matching on `message` is forbidden.
139
+
140
+ **Constants file**: `src/common/constants/error-codes.constant.ts` — single source of truth for all codes.
141
+ Add new codes here before using them. Never use inline strings.
142
+
143
+ **Throw pattern**:
144
+ ```typescript
145
+ // Correct
146
+ import { ERROR_CODES } from '@common/constants/error-codes.constant';
147
+ throw new NotFoundException({ message: 'User not found', code: ERROR_CODES.USER_NOT_FOUND });
148
+
149
+ // Incorrect — missing code
150
+ throw new NotFoundException('User not found');
151
+ ```
152
+
153
+ **Naming convention**: `SCREAMING_SNAKE_CASE`, format `[DOMAIN]_[REASON]`
154
+ - `USER_NOT_FOUND`, `USER_DEACTIVATED`
155
+ - `INVITATION_NOT_FOUND`, `USER_LIMIT_REACHED`
156
+ - `INVALID_CREDENTIALS`, `CONFIRMATION_TOKEN_EXPIRED`
157
+
158
+ **Exceptions to this rule** (do NOT add codes):
159
+ - Validation pipe errors — these carry a per-field `code` in the `errors` array instead
160
+ - Bare `UnauthorizedException()` in the JWT strategy — no info leak on invalid token by design
161
+
162
+ ## Response Transformation
163
+
164
+ {{> slot: response-transformation }}
165
+
166
+ ## Pagination
167
+
168
+ Use a shared `paginatedResponseSchema` helper for all list endpoints that return paginated data.
169
+
170
+ **Rules**:
171
+ * Query params always use `page`/`pageSize` — never expose `skip`/`take` to the client
172
+ * Response always uses the `{ items, total, skip, take }` shape — never `page`/`pageSize`
173
+ * `pageSize` max is 100; default is 50
174
+ * Always use `getManyAndCount()` so `total` is accurate
175
+
176
+ **Service** — convert `page`/`pageSize` to `skip`/`take` and return the envelope:
177
+
178
+ ```typescript
179
+ async list(params: { page: number; pageSize: number }): Promise<{ items: Foo[]; total: number; skip: number; take: number }> {
180
+ const skip = (params.page - 1) * params.pageSize;
181
+ const take = params.pageSize;
182
+ const [items, total] = await qb.skip(skip).take(take).getManyAndCount();
183
+ return { items, total, skip, take };
184
+ }
185
+ ```
186
+
187
+ Query-param coercion and response-DTO declaration are stack-specific:
188
+
189
+ {{> slot: pagination-dtos }}
190
+
191
+ ## Security
192
+
193
+ * Sanitize all user input
194
+ * Use parameterized queries (the ORM handles this automatically)
195
+ * Implement authentication with Guards (`@UseGuards(AuthGuard)`)
196
+ * Implement authorization checks for protected resources
197
+ * Validate file uploads (type, size, content)
198
+ * Never commit secrets, API keys, or credentials to version control
199
+ * Use environment variables for configuration
200
+ * Hash passwords with bcrypt
201
+ * Use HTTPS in production
202
+
203
+ ## Constants
204
+
205
+ File name template: `xx-yy.constant.ts` for `XxYy` constant
206
+
207
+ Files with constants should be placed in the `constants/` folder within the corresponding module (see
208
+ `ai/infrastructure/project-structure.md` for module details)
209
+
210
+ ## Enums
211
+
212
+ File name template: `xx-yy.enum.ts` for `XxYy` enum
213
+
214
+ Files with enums should be placed in the `enums/` folder within the corresponding module (see `ai/infrastructure/project-structure.md` for
215
+ module details)
216
+
217
+ ## No Magic Strings or Numbers
218
+
219
+ **MANDATORY**: Never use raw string or number literals where a named constant or enum value can be used instead. Magic values scatter meaning across the codebase, make refactoring error-prone, and produce silent bugs when a value changes in one place but not another.
220
+
221
+ **Rules**:
222
+ * Any string or number that carries domain meaning and appears in more than one place **must** be a constant or enum
223
+ * Any string or number that carries domain meaning and appears in only one place **should still** be a constant or enum if it is likely to be referenced elsewhere in the future (e.g. audit action names, entity type labels, JWT token types)
224
+ * Raw string comparisons against domain values are always wrong — use the enum
225
+
226
+ **Enums** for closed sets of values (token types, roles, statuses, entity types, action names):
227
+ ```typescript
228
+ // ✅ Correct
229
+ export enum JwtType {
230
+ USER = 'user',
231
+ ADMIN = 'admin',
232
+ IMPERSONATION = 'impersonation',
233
+ }
234
+
235
+ if (payload.type === JwtType.IMPERSONATION) { ... }
236
+
237
+ // ❌ Incorrect
238
+ if (payload.type === 'impersonation') { ... }
239
+ ```
240
+
241
+ **Constants** for single values with no closed set (limits, expiry defaults, config keys):
242
+ ```typescript
243
+ // ✅ Correct — src/feature/user/constants/max-invitation-attempts.constant.ts
244
+ export const MAX_INVITATION_ATTEMPTS = 5;
245
+
246
+ // ❌ Incorrect
247
+ if (attempts > 5) { ... }
248
+ ```
249
+
250
+ **Where to place them**:
251
+ * Enums belong in `enums/` within the owning module — see **Enums** section above
252
+ * Constants belong in `constants/` within the owning module — see **Constants** section above
253
+ * Cross-cutting enums/constants (e.g. `JwtType`, `ERROR_CODES`) belong in `src/common/`
254
+
255
+ **Common categories that always require an enum or constant**:
256
+ * JWT `type` field values
257
+ * Audit action names and entity type labels
258
+ * Platform and tenant role values
259
+ * Any status value with a closed set
260
+ * Any HTTP header name, cookie name, or metadata key used in multiple places
261
+
262
+ ## Code Quality
263
+
264
+ * Follow SOLID principles
265
+ * Keep functions small and focused (single responsibility)
266
+ * Use meaningful variable and function names
267
+ * Add comments only where logic is complex or non-obvious
268
+ * Remove commented-out code before committing
269
+ * Use TypeScript features: interfaces, types, generics, decorators
270
+ * Prefer composition over inheritance
271
+ * Use dependency injection for loose coupling
272
+ * Write self-documenting code
273
+ * Keep code DRY (Don't Repeat Yourself)
@@ -0,0 +1,4 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/architect-discussion-topics }} (A.3).
2
+ `--stack nestjs,typeorm` also appends the typeorm slot (schema, migrations). -->
3
+ - **API design questions** — endpoint naming, request/response shape, pagination or filtering needs
4
+ - **Layering** — what belongs in the feature module vs. the API module
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/architect-mandatory-docs }} (A.2).
2
+ `--stack nestjs,typeorm` also appends the typeorm slot (DATABASE_SCHEMA.md). -->
3
+ - `ai/infrastructure/code-guidelines.md`
4
+ - `ai/infrastructure/project-structure.md`
5
+ - `ai/infrastructure/mappers.md`
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/architect-references }} (A.1).
2
+ Primary-stack-only, not concatenated. -->
3
+ - Backend source files (modules, services, entities, DTOs, controllers)
4
+ - Context docs in `ai/contexts/**` — entity definitions, business logic, feature docs
5
+ - The frontend repo's context docs, if the task touches an API shape the frontend consumes
@@ -0,0 +1,45 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/implementation-reference }} in the core
2
+ developer. Framework areas only. `--stack nestjs,typeorm` also appends
3
+ presets/typeorm/slots/implementation-reference.md (entities, migrations,
4
+ repositories). From snap-proof/backend/ai/agents/node-developer.md. -->
5
+
6
+ Quick reminders for the most common implementation areas. The authoritative source
7
+ is always `ai/infrastructure/code-guidelines.md`.
8
+
9
+ ### Module structure
10
+ - Feature modules in `src/feature/<name>/` — business logic and services
11
+ - API modules in `src/api/modules/<name>/` — HTTP layer only (controllers, API-layer services, request/response DTOs)
12
+ - Register providers, imports, and exports explicitly in every module file
13
+
14
+ ### Controllers
15
+ - Thin controllers: delegate all business logic to services
16
+ - `@Get()` / `@Post()` / `@Put()` / `@Patch()` / `@Delete()` with explicit route paths
17
+ - Bind inputs with `@Body()`, `@Param()`, `@Query()`
18
+ - Apply `@UsePipes(ValidationPipe)` and `@UseGuards()` where required
19
+ - Return consistent response shapes; use response DTOs and mappers
20
+
21
+ ### Services
22
+ - All business logic in feature services; API-layer services handle HTTP concerns only
23
+ - Throw NestJS built-in exceptions: `NotFoundException`, `BadRequestException`, `UnauthorizedException`, …
24
+ - async/await throughout
25
+
26
+ ### DTOs
27
+ - One file per DTO, `.dto.ts` suffix; validation decorators for every property (per the validation-stack variant)
28
+ - `@ApiProperty()` from `@nestjs/swagger` on every property
29
+ - Request DTOs in `src/api/dtos/`; shared/internal DTOs in `src/feature/dtos/`
30
+
31
+ ### Error handling
32
+ - Use NestJS exceptions — never expose raw DB errors or stack traces
33
+ - Custom exceptions extend `HttpException` only when no built-in fits
34
+ - Log errors at service level; never log sensitive data
35
+
36
+ ### Testing
37
+ - Unit tests for services: `feature-name.service.spec.ts`
38
+ - `Test.createTestingModule()` for NestJS context; mock dependencies
39
+ - Cover happy paths, not-found cases, and validation failures
40
+
41
+ ### File naming
42
+ - `feature-name.module.ts` / `.service.ts` / `.controller.ts`
43
+ - DTOs: `create-feature.dto.ts`, `update-feature.dto.ts`, `feature-response.dto.ts`
44
+ - Tests: `feature-name.service.spec.ts`
45
+ - All kebab-case
@@ -0,0 +1,11 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/key-patterns }} in the core architect
2
+ and developer. `--stack nestjs,typeorm` also appends the typeorm slot. -->
3
+
4
+ Module and layer conventions: **`ai/infrastructure/project-structure.md`** — feature
5
+ modules (`src/feature/`) vs. API modules (`src/api/modules/`), service/repository
6
+ placement. Plans should follow the same shape.
7
+
8
+ Code conventions (naming, DTOs, validation, decorators, error handling):
9
+ **`ai/infrastructure/code-guidelines.md`**.
10
+
11
+ Mapper conventions: **`ai/infrastructure/mappers.md`**.
@@ -0,0 +1,12 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/plan-step-ordering }} in the core
2
+ architect's Plan format. This slot is *not* concatenated across stack entries
3
+ — it is one coherent sequence, taken from the primary stack (stack[0]). It
4
+ assumes an ORM in step 1; a bare `--stack nestjs` repo trims that step.
5
+ From node-architect.md. -->
6
+ 1. Entity / migration changes
7
+ 2. Feature module — service and repository setup
8
+ 3. DTOs and validation decorators
9
+ 4. API module — controller and endpoint implementation
10
+ 5. Error handling and edge cases
11
+ 6. Unit and integration test hooks
12
+ 7. Context doc updates
@@ -0,0 +1,12 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/review-checklist }} in the core reviewer.
2
+ Framework rules only. A repo on `--stack nestjs,typeorm` also gets the ORM
3
+ rows from presets/typeorm/slots/review-checklist.md (the renderer concatenates
4
+ this slot from every stack entry). Response-mapping and validation-decorator
5
+ rows come from the chosen validation-stack variant, not here. -->
6
+
7
+ | No `any` type — use proper TypeScript types | All changed files |
8
+ | Enums in `enums/` folder, constants in `constants/` folder, kebab-case filenames | Feature module |
9
+ | No magic strings — use enum values or named constants | Anywhere |
10
+ | API modules contain no business logic (thin controllers, delegate to feature services) | Controller / API service files |
11
+ | Feature modules export only the services that API modules need | Module files |
12
+ | NestJS built-in exceptions used for errors (`NotFoundException`, `BadRequestException`, …) | Service and controller files |
@@ -0,0 +1,120 @@
1
+ <!-- cairn variant: validation-stack = class-validator. Fills the dto-naming,
2
+ dto-definition, validation-stack, controller-response,
3
+ response-transformation and pagination-dtos slots in
4
+ presets/node/code-guidelines.md.
5
+ In use by: snap-backend. Requires `class-validator` + `class-transformer`
6
+ and the project's own `@ResponseMapper` decorator. -->
7
+
8
+ ## slot: dto-naming
9
+
10
+ * Use operation-prefixed names for incoming request data:
11
+ - `CreateProjectDto`, `UpdateUserDto`
12
+ - File: `create-feature.dto.ts`, `update-feature.dto.ts`
13
+ * Use the `Response` suffix for server response shapes:
14
+ - `UserResponse`, `ProjectListResponse`
15
+ - File: `feature-response.dto.ts`
16
+
17
+ ## slot: dto-definition
18
+
19
+ * Use class-validator decorators: `@IsString()`, `@IsNumber()`, `@IsEmail()`, `@IsOptional()`, `@IsNotEmpty()`
20
+ * Use class-transformer decorators: `@Type()`, `@Exclude()`, `@Expose()`
21
+ * Add Swagger documentation: `@ApiProperty()`, `@ApiPropertyOptional()`
22
+ * Provide validation error messages where appropriate
23
+
24
+ ## slot: validation-stack
25
+
26
+ * Use class-validator decorators on all DTO properties
27
+ * Use `ValidationPipe` globally or per-controller
28
+ * Create custom validators for complex validation logic
29
+
30
+ ## slot: controller-response
31
+
32
+ * Apply `@UsePipes(ValidationPipe)` for automatic DTO validation
33
+ * Annotate the response shape with `@ResponseMapper(ResponseDtoClass)` on every endpoint that returns data
34
+
35
+ ## slot: response-transformation
36
+
37
+ **IMPORTANT**: The `@ResponseMapper(ResponseDtoClass)` decorator automatically handles entity-to-DTO transformation using class-transformer.
38
+
39
+ **How it works**:
40
+ * The decorator uses `@Expose()` decorators in the response DTO to pick which fields to include
41
+ * The decorator uses `@Type()` decorators to handle nested object transformations
42
+ * You **DO NOT** need to manually create mapper functions to pick fields
43
+
44
+ **Best Practices**:
45
+ * Return entities or plain objects from service methods
46
+ * Only perform minimal transformations needed for data structure (e.g., flattening nested properties)
47
+ * Let `@ResponseMapper` handle field selection and type transformation
48
+ * Do **NOT** create separate mapper functions just to pick fields - the decorator does this automatically
49
+
50
+ **Example - Correct approach**:
51
+ ```typescript
52
+ // Service method - minimal transformation
53
+ async getDevicesByProjects() {
54
+ const devices = await this.siteDeviceService.getDevicesByProjects();
55
+
56
+ // Only flatten nested structures that don't match DTO
57
+ return devices.map(device => ({
58
+ ...device,
59
+ site: device.site ? {
60
+ ...device.site,
61
+ city: device.site.city?.name || null // Flatten CityEntity to string
62
+ } : null,
63
+ }));
64
+ }
65
+
66
+ // Controller - @ResponseMapper handles field picking
67
+ @Get('by-projects')
68
+ @ResponseMapper(DeviceWithProjectsResponse)
69
+ async getDevicesByProjects(@Query() query: QueryDto) {
70
+ return this.service.getDevicesByProjects(query);
71
+ }
72
+
73
+ // Response DTO - @Expose() picks fields automatically
74
+ export class DeviceWithProjectsResponse {
75
+ @Expose() id: string;
76
+ @Expose() description: string;
77
+ // Only exposed fields will be returned
78
+ }
79
+ ```
80
+
81
+ **Example - Incorrect approach** (avoid this):
82
+ ```typescript
83
+ // ❌ DON'T create mapper functions just to pick fields
84
+ function mapToResponse(device) {
85
+ return {
86
+ id: device.id, // @ResponseMapper does this
87
+ description: device.description, // automatically
88
+ };
89
+ }
90
+ ```
91
+
92
+ ## slot: pagination-dtos
93
+
94
+ **Query params** — declare `page` and `pageSize` with `@Type(() => Number)` so query strings coerce:
95
+
96
+ ```typescript
97
+ export class FooListQuery {
98
+ @Type(() => Number)
99
+ @IsInt()
100
+ @Min(1)
101
+ page = 1;
102
+
103
+ @Type(() => Number)
104
+ @IsInt()
105
+ @Min(1)
106
+ @Max(100)
107
+ pageSize = 50;
108
+ }
109
+ ```
110
+
111
+ **Response DTO** — expose the envelope fields and type the items:
112
+
113
+ ```typescript
114
+ export class FooListResponse {
115
+ @Expose() @Type(() => FooResponse) items: FooResponse[];
116
+ @Expose() total: number;
117
+ @Expose() skip: number;
118
+ @Expose() take: number;
119
+ }
120
+ ```